Example: Adding support for a custom promotion type

This scenario adds support for an additional promotion type.

  1. Define a new XML model for the customized promotion type.
  2. Analyze the new attributes the customized promotion needs in order to complete a purchase condition including reward. If a customer buys anything from the personalized gift category, display an advertisement for a promotion that gives a scented holiday candle (single SKU) for $5.00. This is a fixed cost category level promotion.
    The information that purchase condition and reward required will be:
    1. Category ID (for example: name)
    2. Quantity required for this category purchase.
    3. Promoted product (for example: holiday candle's SKU)
    4. Given quantity of promoted product (default 1)
    5. Fixed cost assigned to this promoted product.
  3. Create a Java interface which defines all of the constants for the new discount type and new attributes for promotions user interface, including JSP files and data beans. To create this interface:
    1. Open the WebSphere Commerce development environment (Start > Programs > IBM WebSphere Commerce development environment)
    2. Navigate to the WebSphereCommerceServerExtensionsLogic project.
    3. Right-click on the project, and select New, and then Package.
    4. In the Name Field on the New Java Package dialog, enter com.myCompany.epromotion.logic, where myCompany represents some custom directory name. Packages created by IBM, and included in WebSphere Commerce follow a naming convention which begins, "com.ibm..."
    5. Click Finish to create the new package.
    6. Right-click on the new package, and select New, and then Interface.
    7. In the Name field, enter ExtendedPromotionConstants.
    8. Click Finish. The file opens in the editor.
    9. Update the file so that it matches the following example:
      
      package com.myCompany.epromotion.logic; 
      
      public interface ExtendedPromotionConstants extends com.ibm.commerce.tools.epromotion.RLConstants {
           // The new promotion types below are used for the promotion factory to identify 
           // each new promotion object.
           // These new types have to be registered in the RLPromotion.xml in order to be picked up
           // by Promotion Component Configuration
      
              public static final String PROMOTION_CATEGORYLEVELPWP = "CategoryLevelPurchaseWithPurchase";
              public static final String PROMOTION_PRODUCTLEVELPWP = "ProductLevelPurchaseWithPurchase";
              public static final String PROMOTION_ITEMLEVELPWP = "ItemLevelPurchaseWithPurchase";
           // The attributes below are for CategoryLevelPurchaseWithPurchase UI element naming convention,
           // the name should be matching with the one in UI databean in order to allow Tools FrameWork Java
           // Script object to pick up.
           // These attributes could also be reused by other types if the promotion condition is similar.
           
              public static final String PROMOTION_REQUIREDITEMQTY = "rlRequiredItemQty";
              public static final String PROMOTION_PROMOTEDITEMQTY = "rlPromotedItemQty";
              public static final String PROMOTION_PROMOTEDPRODUCTID = "rlPromotedProductId";
              public static final String PROMOTION_FIXCOST = "rlFixCost";
      }    
      
    10. Save the file, but do not close the development environment.
  4. Create a Java object that represents the custom promotion type. This object has to present the new purchase condition and reward, and handle the XML input and output. This object will implement the interface created in step 3. To create this object:
    1. Navigate to the WebSphereCommerceServerExtensionsLogic project.
    2. Navigate to the src folder.
    3. Right-click on the src folder, and select New, and then Package.
    4. In the Name Field on the New Java Package dialog, enter a unique name. For the purposes of this scenario, enter com.myCompany.epromotion.implementations, where myCompany represents some custom directory name.
    5. Click Finish to create the package.
    6. Right-click on the com.myCompany.epromotion.implementations package, and select New, and then Class.
    7. In the Name field on the New Java Class dialog, enter CategoryLevelPurchaseWithPurchase.
    8. In the Superclass field on the New Java Object dialog, enter com.ibm.commerce.tools.epromotion.RLCategoryLevelPromotion.
    9. Click Finish. The file opens in the editor.
    10. Update the file so that it matches the following example:
      
      package com.myCompany.epromotion.implementations;
      
      /**
       * The puchase condition in this sample is based on the category, therefore, this
       * Class extends {@link RLCategoryLevelPromotion}.
       * 
       * 
       * This XML will be generated in the {@link #toSpecificXML} method.
       * Also, all the fields in this class should be able to populate based on this XML through
       * {@link #populatePromotionSpecificDataFrom(String)}
       */
      
      import java.util.*;
      import java.text.Collator;
      import com.ibm.commerce.ras.*;
      import com.ibm.commerce.tools.epromotion.*;
      import com.ibm.commerce.tools.epromotion.util.*;
      import com.ibm.commerce.exception.*;
      
      import javax.xml.parsers.*;
      import org.xml.sax.InputSource;
      import org.w3c.dom.*;
      
      public class CategoryLevelPurchaseWithPurchase
         extends com.ibm.commerce.tools.epromotion.RLCategoryLevelPromotion
         implements com.myCompany.epromotion.logic.ExtendedPromotionConstants {
      
         /**
          * Customization: Define all the promotion purchase condition and reward attributes.
          * 1. The methods to retrieve the required item identification is in the parent class.
          * @see RLCategoryLevelPromotion#setCatalogGroupIDs(Vector)
          * 2. Terminology: based on the sample promotion: giving B a discount if purchase a number of A
          * A is the required item.
          * B is the promoted item.
          * 3. In this class, A is a category entity, B is a product/item based on user input.
          * The customized JSP, needs to support both search functions to allow choose product/item.
          */
      
         // The quantity required for the purchased item in order to get discount on promoted item.
         private int requiredItemQty;
        
         // The quantity of promoted item this promotion rule will give for a fixed cost.
         // default is 1 (option): which means fixed cost for each promoted item.
         // 2 means fixed cost for every 2 items...
         private int promotedItemQty=1;
      
         // The catalog entry identification of the promoted item
         private String promotedProductId;
       
         // The fix cost for the promoted items,
         // This value should be a String representation of Decimal
         private String fixCost;
       
         /**
          * This default promotion constructor.
          * There is no need to customized this constructor unless you have your own attributes
          * for new promotions, which should be initialized during object creation.
          */
         public CategoryLevelPurchaseWithPurchase() {
            super();
         }
       
         /**
          * It should always return the toSpecificXML(), there is no need customize it.
          */
         public String generatePromotionSpecificRuleXML() {
            return toSpecificXML();
         }
       
         /**
          * This method should be customized according to the new promotion purchase condition.
          * This method should generate the portion of the promotion XML, which fully describes
          * the purchase condition and reward relationship.
          */
         public String toSpecificXML() {
            final String methodName = "toSpecificXML()";
            // XmlHelper provides some utility function for XML String generation.
            XmlHelper helper = new XmlHelper();
            StringBuffer buffer = new StringBuffer();
            buffer
               .append("<PurchaseCondition impl=\"com.ibm.commerce.marketing.promotion.condition.PurchaseCondition\">")
               .append("<Pattern impl=\"com.ibm.commerce.marketing.promotion.condition.Pattern\">")
               .append("<Constraint impl=\"com.ibm.commerce.marketing.promotion.condition.Constraint\">")
               .append("<WeightedRange impl=\"com.ibm.commerce.marketing.promotion.condition.WeightedRange\">")
               .append("<LowerBound>")
               .append(this.getRequiredItemQty())
               .append("</LowerBound>")
               .append("<UpperBound>")
               .append(this.getRequiredItemQty())
               .append("</UpperBound>")
               .append("<Weight>1</Weight>")
               .append("</WeightedRange>")
               .append("<FilterChain impl=\"com.ibm.commerce.marketing.promotion.condition.FilterChain\">")
               .append(this.getRequiredItemFilter())
               .append("</FilterChain>")
               .append("</Constraint>")
               .append("<Constraint impl=\"com.ibm.commerce.marketing.promotion.condition.Constraint\">")
               .append("<WeightedRange impl=\"com.ibm.commerce.marketing.promotion.condition.WeightedRange\">")
               .append("<LowerBound>")
               .append(this.getPromotedItemQty())
               .append("</LowerBound>")
               .append("<UpperBound>")
               .append(this.getPromotedItemQty())
               .append("</UpperBound>")
               .append("<Weight>1</Weight>")
               .append("</WeightedRange>")
               .append("<FilterChain impl=\"com.ibm.commerce.marketing.promotion.condition.FilterChain\">")
               .append(this.getPromotedItemFilter())
               .append("</FilterChain>")
               .append("</Constraint>")
               .append("</Pattern>")
               .append("<Distribution impl=\"com.ibm.commerce.marketing.promotion.reward.Distribution\">")
               .append("<Type>Volume</Type>")
               .append("<Base>Quantity</Base>")
               .append("<Currency/>")
               .append("<Range impl=\"com.ibm.commerce.marketing.promotion.reward.DistributionRange\">")
               .append("<UpperBound>-1</UpperBound>")
               .append("<LowerBound>1</LowerBound>")
               .append("<RewardChoice>")
               .append("<Reward impl=\"com.ibm.commerce.marketing.promotion.reward.DefaultReward\">")
               .append("<AdjustmentFunction impl=\"com.ibm.commerce.marketing.promotion.reward.AdjustmentFunction\">")
               .append("<FilterChain impl=\"com.ibm.commerce.marketing.promotion.condition.FilterChain\">")
               .append(this.getPromotedItemFilter())
               .append("</FilterChain>")
               .append("<Adjustment impl=\"com.ibm.commerce.marketing.promotion.reward.FixedCostAdjustment\">")
               .append("<FixedCost>")
               .append(this.getFixCost())
               .append("</FixedCost>")
               .append("<Currency>")
               .append(this.getCurrency())
               .append("</Currency>")
               .append("<AdjustmentType>IndividualAffectedItems</AdjustmentType>")
               .append("</Adjustment>")
               .append("</AdjustmentFunction>")
               .append("</Reward>")
               .append("</RewardChoice>")
               .append("</Range>")
               .append("<PatternFilter impl=\"com.ibm.commerce.marketing.promotion.condition.DummyPatternFilter\" />")
               .append("</Distribution>")
               .append("</PurchaseCondition>");
              
               return buffer.toString();
            }
          
            public void fromSpecificXML(String xml) {
               final String methodName = "fromSpecificXML()";
         
               EproUtil util = new EproUtil();
               Collator collator = Collator.getInstance();
               // Get the promoted item from reward 
               String tempPromotedSKU =
                  XmlHelper
                     .getElementTextValueInNode(
                        XmlHelper.getXMLDocument(xml),
                        "Reward",
                        "SKU")
                     .firstElement()
                     .toString();
               String tempPromotedDN =
                  XmlHelper
                     .getElementTextValueInNode(
                        XmlHelper.getXMLDocument(xml),
                        "Reward",
                        "DN")
                     .firstElement()
                     .toString();
               try {
                  promotedProductId =
                     util.getCatEntryId(tempPromotedSKU, tempPromotedDN);
               } catch (ECException e) {
                  ECTrace.trace(
                     ECTraceIdentifiers.COMPONENT_MERCHANDISING,
                     getClass().getName(),
                     methodName,
                     e.toString());
                  // exception is handled inside the util class 
               }
               // Get the promoted item cost from the reward adjustment
               fixCost =
                  XmlHelper
                     .getElementTextValueInNode(
                        XmlHelper.getXMLDocument(xml),
                        "Adjustment",
                        "FixedCost")
                     .firstElement()
                     .toString();
               this.setCurrency(
                  XmlHelper
                     .getElementTextValueInNode(
                        XmlHelper.getXMLDocument(xml),
                        "Adjustment",
                        "Currency")
                     .firstElement()
                     .toString());
       
            // Get information from Pattern -- Constraint Node List
            NodeList nodeList =
               XmlHelper.getXMLDocument(xml).getElementsByTagName("Constraint");
            int size = nodeList.getLength();
            for (int i = 0; i < size; i++) {
               if (nodeList.item(i).hasChildNodes()
                  && (nodeList.item(i).getNodeType() == Node.ELEMENT_NODE)) {
                  Element element = (Element) nodeList.item(i);
                  NodeList catentryKeys =
                     element.getElementsByTagName("IncludeCatEntryKey");
                  if (catentryKeys.getLength() > 0) {
                     // this is the promoted item filter since it is the SKU Filter in this sample XML.
                     promotedItemQty =
                        Integer.parseInt(
                           element
                              .getElementsByTagName("LowerBound")
                              .item(0)
                              .getFirstChild()
                              .getNodeValue());
                  } 
                  NodeList categoryKeys =
                     element.getElementsByTagName("IncludeCategory");
                  if (categoryKeys.getLength() > 0) {
                     // This is the required category filter since it is the category Filter in the sample XML.
                     // The section below populates the required item quantity from XML.
                     requiredItemQty =
                        Integer.parseInt(
                           element
                              .getElementsByTagName("LowerBound")
                              .item(0)
                              .getFirstChild()
                              .getNodeValue());
                     // The section below populates the catalog group IDs
                     NodeList names = element.getElementsByTagName("Name");
                     NodeList dns = element.getElementsByTagName("DN");
                     Vector cgpIdentifiers = new Vector();
                     Vector cgpIds = new Vector();
                     for (int j = 0; j < names.getLength(); j++) {
                        try {
                           String tempCatGrpId =
                              util.getCatGroupId(
                                 names
                                    .item(j)
                                    .getFirstChild()
                                    .getNodeValue(),
                                 dns.item(j).getFirstChild().getNodeValue());
                           if (tempCatGrpId != null
                              && (collator.compare(tempCatGrpId.trim(), "")
                                 != 0)) {
                              cgpIds.addElement(tempCatGrpId);
                              cgpIdentifiers.addElement(
                                 names
                                    .item(j)
                                    .getFirstChild()
                                    .getNodeValue());
                           } 
                        } catch (ECException e) {
                           ECTrace.trace(
                              ECTraceIdentifiers.COMPONENT_MERCHANDISING,
                              getClass().getName(),
                              methodName,
                              e.toString());
                        } 
                     }
                     this.setCatalogGroupIDs(cgpIds);
                     this.setCatgpIdentifiers(cgpIdentifiers);
                  }   
               }
            } 
         } 
       
         public void populatePromotionSpecificDataFrom(Map h)
            throws com.ibm.commerce.exception.ParameterNotFoundException {
            requiredItemQty =
               EproUtil.toInt(
                  EproUtil.doCheckParameterFound(h, PROMOTION_REQUIREDITEMQTY));
            promotedItemQty =
               EproUtil.toInt(
                  EproUtil.doCheckParameterFound(h, PROMOTION_PROMOTEDITEMQTY));
            promotedProductId =
               EproUtil
                  .doCheckParameterFound(h, PROMOTION_PROMOTEDPRODUCTID)
                  .toString();
            fixCost =
               EproUtil.doCheckParameterFound(h, PROMOTION_FIXCOST).toString();
         } 
      
         public void populatePromotionSpecificDataFrom(String s) {
            // This method always call fromSpecificXML(xml) internally.
            this.fromSpecificXML(s);
         }
      
         /**
          * Set the quantity required for the purchased item in order to get discount on promoted item.
          * @param newRequiredItemQty the required item quantity passed by user.
          */
         public void setRequiredItemQty(int newRequiredItemQty) {
            this.requiredItemQty = newRequiredItemQty;
         } 
      
         /**
          * Get the quantity required for the purchased item in order to get discount on promoted item.
          * @return requiredItemQty
          */
         public int getRequiredItemQty() {
           return this.requiredItemQty;
         }
         
         /**
          * Set the quantity of promoted item this promotion rule will give.
          * @param newPromotedItemQty
          */
         public void setPromotedItemQty(int newPromotedItemQty) {
            this.promotedItemQty = newPromotedItemQty;
         }
         
         /**
          * Get the quantity of promoted item this promotion rule will give.
          * @return promotedItemQty
          */
         public int getPromotedItemQty() {
            return this.promotedItemQty;
         }
        
         /**
          * Set the catalog entry identification of the promoted item.
          * @param newPromotedProductId
          */
         public void setPromotedProductId(String newPromotedProductId) {
            this.promotedProductId = newPromotedProductId;
         }
        
         /**
          * Get the catalog entry identification of the promoted item.
          * @return promotedProductId
          */
         public String getPromotedProductId() {
            return this.promotedProductId;
         }
      
         /**
          * Set the fix cost for the promoted item.
          * @param newFixCost
          */
         public void setFixCost(String newFixCost) {
            this.fixCost = newFixCost;
         }
        
         /**
          * Get the fix cost for the promoted item,
          * @return fixCost
          */
         public String getFixCost() {
           return this.fixCost;
         }
       
         /**
          * This method is generating the Filter XML object for the promoted item.
          * @return promotedItemFilter the XML String
          */
         protected String getPromotedItemFilter() {
            final String methodName = "getPromotedItemFilter()";
            XmlHelper helper = new XmlHelper();
            StringBuffer buffer = new StringBuffer();
            buffer
               .append("<Filter impl=\"com.ibm.commerce.marketing.promotion.condition.MultiSKUFilter\">")
               .append("<IncludeCatEntryKey>")
               .append("<CatalogEntryKey>");
            try {
               buffer.append(
                  helper.generateProductXMLStringByCatentryId(
                     "SKU",
                     "DN",
                     this.getPromotedProductId()));
            } catch (ECException e) {
               ECTrace.trace(
                  ECTraceIdentifiers.COMPONENT_MERCHANDISING,
                  getClass().getName(),
                  methodName,
                  "CatalogEntryKey Generation Error "
                  + this.getPromotedProductId()
                  + e.toString());
            }
            buffer.append(";</CatalogEntryKey>").append(
               "</IncludeCatEntryKey>").append(
               "</Filter>");
            return buffer.toString();
         }
         protected String getRequiredItemFilter() {
            final String methodName = "getRequiredItemFilter()";
            XmlHelper helper = new XmlHelper();
            StringBuffer buffer = new StringBuffer();
            // The parent class is actually getting the targetted category identification for this promotion.
            buffer.append(
               "<Filter impl=\"com.ibm.commerce.marketing.promotion.condition.CategoryFilter\">");
            for (int i = 0; i < this.getCatalogGroupIDs().size(); i++) {
               buffer.append("<IncludeCategory>").append("<CategoryKey>");
               try {
                  buffer.append(
                     helper.generateCategoryXMLStringByCatgroupId(
                        "Name",
                        "DN",
                        this.getCatalogGroupIDs().elementAt(i).toString()));
               } catch (ECException e) {
                  ECTrace.trace(
                     ECTraceIdentifiers.COMPONENT_MERCHANDISING,
                     getClass().getName(),
                     methodName,
                     "Category Key Generation Error "
                        + this.getCatalogGroupIDs().elementAt(i).toString()
                        + e.toString());
               }
               buffer.append("</CategoryKey>").append("</IncludeCategory>");
           }
           buffer.append("</Filter>");
           return buffer.toString();
         }
      }
      
    11. Save the file, but do not close the development environment.
  5. Extend the RLProductDiscountDataBean such that it supports the new promotion type. This new data bean populates the new attributes into the notebook and summary pages. To update this data bean:
    1. In the WebSphere Commerce development environment, navigate to the WebSphereCommerceServerExtensionsLogic project.
    2. Right-click on the src folder, and select New, and then Package.
    3. In the Name Field on the New Java Package dialog, enter com.myCompany.epromotion.databeans, where myCompany represents some custom directory name.
    4. Right-click on the new package, and select New, and then Class.
    5. In the Name field, enter ExtendedPromotionDataBean.
    6. In the Superclass field, enter the following: com.ibm.commerce.tools.epromotion.databeans.RLProductDiscountDataBean.
    7. Click Finish. The file opens in the editor.
    8. Update the databean by entering the following code:
      
      package com.myCompany.epromotion.databeans;
      
      import com.ibm.commerce.ras.*;
      import com.ibm.commerce.tools.epromotion.util.*;
      import com.ibm.commerce.tools.epromotion.*;
      import com.ibm.commerce.tools.epromotion.implementations.*;
      import com.ibm.commerce.fulfillment.objects.*;
      import com.myCompany.epromotion.implementations.*;
      import com.myCompany.epromotion.logic.*;
      import com.ibm.commerce.tools.epromotion.databeans.*;
      
      public class ExtendedPromotionDataBean extends com.ibm.commerce.tools.epromotion.databeans.RLProductDiscountDataBean
         implements com.myCompany.epromotion.logic.ExtendedPromotionConstants {
      
            public ExtendedPromotionDataBean() {
               super();
            }
      
            private int rlRequiredItemQty;
            private int rlPromotedItemQty;
            private String rlPromotedProductId;
            private String rlFixCost;
        
            public void setRlRequiredItemQty(int newRlRequiredItemQty) {
               this.rlRequiredItemQty = newRlRequiredItemQty;
            }
            public void setRlPromotedItemQty(int newRlPromotedItemQty) {
               this.rlPromotedItemQty = newRlPromotedItemQty;
            }
            public void setRlPromotedProductId(String newRlPromotedProductId) {
               this.rlPromotedProductId = newRlPromotedProductId;
            }
            public void setRlFixCost(String newRlFixCost) {
               this.rlFixCost = newRlFixCost;
            }
            public int getRlRequiredItemQty() {
               return this.rlRequiredItemQty;
            }
            public int getRlPromotedItemQty() {
               return this.rlPromotedItemQty;
            }
            public String getRlPromotedProductId() {
               return this.rlPromotedProductId;
            }
            public String getRlFixCost() {
               return this.rlFixCost;
            }
            /**
             * Populate method should be implemented based on the new attributes and type.
             * This populate method will handled by TFW parent frame once it is specified
             * in the Notebook.xml configuration.
             */
            public void populate() throws Exception {
               final String methodName = "populate";
               // call super.populate() to get all the RLPromotion object atributes
               super.populate();
               ECTrace.entry(
                  ECTraceIdentifiers.COMPONENT_MERCHANDISING,
                     getClass().getName(),
                     methodName);
               if (this.getCalcodeId() != null) {
                  EproUtil util = new EproUtil();
                  RLPromotionBean rlPromotionBean = new RLPromotionBean();
                  rlPromotionBean.setCalCodeId(this.getCalcodeId());
                  rlPromotionBean.setCommandContext(this.commandContext);
                  rlPromotionBean.setRequestProperties(this.requestProperties);
                  rlPromotionBean.populate();
       
                  // populate the ExtendedPromotionDataBean with RLPromotionBean values
                  RLPromotion rlPromotion = rlPromotionBean.getRLPromotion();
         
                  // The part below is customized for each extended type
                  if (this
                     .getRlPromotionType()
                     .equalsIgnoreCase(PROMOTION_CATEGORYLEVELPWP)) {
                     if (rlPromotion instanceof CategoryLevelPurchaseWithPurchase) {
                        CategoryLevelPurchaseWithPurchase obj =
                        (CategoryLevelPurchaseWithPurchase) rlPromotion;
                        if (obj != null) {
                           this.setRlPromotionCatEntryType("Category");
                           this.setRlPromotionMerchandiseType("Category");
                              if (obj.getCatgpIdentifiers() != null) {
                                 int numOfCatGrps = obj.getCatgpIdentifiers().size();
                                 String[] tempArry = new String[numOfCatGrps];
                                 obj.getCatgpIdentifiers().copyInto(tempArry);
                                 this.setRlPromotionCatGroupCode(tempArry);
                              }
                              if (obj.getCatalogGroupIDs() != null) {
                                 int numOfCatGrps = obj.getCatalogGroupIDs().size();
                                 String[] tempArry = new String[numOfCatGrps];
                                 obj.getCatalogGroupIDs().copyInto(tempArry);
                                 this.setRlPromotionCatGroupID(tempArry);
                              }
                              this.setRlRequiredItemQty(obj.getRequiredItemQty());
                              this.setRlPromotedItemQty(obj.getPromotedItemQty());
                              this.setRlFixCost(obj.getFixCost());
                              this.setRlPromotedProductId(obj.getPromotedProductId());
                              // RLDiscountItemSku is using to retrieve the sku for displaying.
                              this.setRlDiscountItemSku(util.getSKU(obj.getPromotedProductId()));
                        }
                     }
                  }
                  // other customized types will be added here.
               }
            }
         }
      
    9. Save the file, but do not close the development environment.
  6. Update the promotion notebook XML definition file to reflect the custom promotion type. Again, to protect your customized data, it must be created in a safe place, separate from the WebSphere Commerce assets. This procedure creates a new Promotion notebook XML definition file. This is not done within the development environment. To update this notebook:
    1. Create a new directory. For the purposes of this scenario, name the directory, WCDE_installdir/xml/ myCustomXML/, where myCustomXML represents some custom directory name.
    2. Repeat this process until you have created the following path:
      
      WCDE_installdir/xml/myCustomXML/tools/epromotion/
      
    3. Copy the WCDE_installdir/xml/tools/epromotion/RLPromotionNotebook.xml file, and move the file to the WCDE_installdir/xml/myCustomXML/tools/epromotion/ directory. Do not change the file name.
    4. Open the RLPromotionNotebook.xml file in an editor.
    5. Update the XML definition to load the custom data bean. Scroll down to the <databean> element. Change the code so that it matches the following example:
      
      <databean name="rlpromotion"
      >myCompany.promotions.databeans.ExtendedPromotionDataBean" 
         stoplevel="2" />
      

      Notice that a new attribute, Add stoplevel = "2", was also added. This attribute indicates that the JavaScript object must also populate the attributes of this data bean's parent data bean.

    6. Save the file.
  7. Creating a custom XML file in a custom directory requires that you update the instance XMLPath setting. This setting governs the locations in which the application will attempt to locate XML files. It functions in a manner similar to a Java classpath setting. This is not done within the development environment. To update the XMLPath setting:
    1. Change to the WCDE_installdir/xml/config directory.
    2. Open the wc-server.xml file in an editor.
    3. Scroll down to the <ToolsGeneralConfig> section of the file, and update the XMLPath by specifying your custom directory name to the value. For example, if the original XMLPath value is
      
      XMLPath="tools;tools/devtools;WEB-INF/xml/tools;WEB_INF"
      
      and your custom directory is myCustomXML, then change the XMLPath value to
      
      XMLPath="myCustomXML;myCustomXML/tools;tools;tools/devtools;WEB-INF/xml/tools;WEB_INF"
      
    4. Save the file.

    Changing the XMLPath setting in the instance configuration file enable this customization only for the single instance. All other instances will not include this new button. If you have multiple instances to which you want to apply the customization, you must repeat this step for each instance.

    Attention

    Applying fix packs, or performing migration may overwrite any changes made to this file.

  8. Create a new JSP file which allows end users to create or modify the new customized type, including the purchase condition and reward. To create this JSP file:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/tools directory.
    2. Right-click on the folder, and select New, and then Folder. In the Folder name field, enter custom.
    3. Right-click on the Web Content/tools/custom folder, and select New, and then JSP File.
    4. In the File Name field on the New JSP File dialog, enter CustomizedPWP.jsp.
    5. Navigate to the Web Content/tools/custom folder.
    6. Double-click the new file to open it in an editor.
    7. Update your JSP file with the following:
      
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <%@ page import="com.ibm.commerce.catalog.beans.CatalogEntryDataBean" %>
      <%@ page import="com.ibm.commerce.tools.epromotion.databeans.RLCatEntrySearchListDataBean" %>
      <%@ page import="com.myCompany.epromotion.databeans.ExtendedPromotionDataBean" %>
      <%@ page import="com.myCompany.epromotion.logic.ExtendedPromotionConstants" %>
      
      <%@include file="../epromotion/epromotionCommon.jsp" %>
      
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <title>Category Level Purchase with Purchase</title>
      <jsp:useBean id="catBean" scope="page" class="com.ibm.commerce.catalog.beans.CatalogEntryDataBean" />
      <jsp:setProperty property="*" name="catBean" />
      <script>
      var needValidation = false;
      
      </script>
      <%= fPromoHeader%>
      <%
         CommandContext commandContext = (CommandContext) request.getAttribute(ECConstants.EC_COMMANDCONTEXT);
         String storeId = commandContext.getStoreId().toString();
         String catEntryType = null;
         String catEntryId = null;
      
         // ### this javaFlagNotFound is used for javascript validation method to check if the user input is valid or not.
         boolean javaFlagNotFound = true;
         // ### this discountSKU is used to store the user manually input and for SKU validation.
         String discountSKU = request.getParameter("discountSKU");
         String calCodeId = request.getParameter("calCodeId");
         String gotoPanelName = request.getParameter("gotoPanelName");
      
         if ((discountSKU != null) && !(discountSKU.trim().equals("")) )
         {
            RLCatEntrySearchListDataBean searchBean = new RLCatEntrySearchListDataBean();
            searchBean.setCommandContext(commandContext);
      
            searchBean.setSku(discountSKU);
            searchBean.setSkuCaseSensitive("yes");
            searchBean.setSkuOperator("EQUAL");
      
            com.ibm.commerce.beans.DataBeanManager.activate(searchBean, request);
      
            int resultCount =  0;
      
            // Get results from the search query
            CatalogEntryDataBean catalogEntries[] = null;
            catalogEntries = searchBean.getResultList();
            if (catalogEntries != null) {
               resultCount = catalogEntries.length;
            }
            if (resultCount > 0){
               catBean = catalogEntries[0];
               catBean.setCommandContext(searchBean.getCommandContext());
               javaFlagNotFound = false;
               catEntryType = catBean.getType();
               catEntryId = catBean.getCatalogEntryID();
               // This result could be any SKU, including Item/Product/Package
            }
            else {
               javaFlagNotFound = true;
            }
         }
      %>
      
      <script src="/wcs/javascript/tools/common/Util.js">
      </script>
      
      <script language="JavaScript">
      
      var calCodeId = null;
      needValidation = <%= javaFlagNotFound%>;
      
      function initializeState() {
         var cType = '<%=UIUtil.toJavaScript(catEntryType)%>';
         var cId = '<%=UIUtil.toJavaScript(catEntryId)%>';
         var discountSku = '<%=UIUtil.toJavaScript(discountSKU)%>';
         var nextPanel = '<%=UIUtil.toJavaScript(gotoPanelName)%>'
        
         var rlPromo = top.getData("RLPromotion");
         if (rlPromo != null) {
            calCodeId = rlPromo.<%= RLConstants.EC_CALCODE_ID %>;
            parent.put("<%= RLConstants.RLPROMOTION %>",rlPromo);
      
            var pgArray =top.getData("RLPWPPageArray",0);
            if(pgArray != null){
               parent.pageArray = pgArray;
            }
              
         }
         else { 
            if (parent.get) {
               var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
               calCodeId = o.<%= RLConstants.EC_CALCODE_ID %>;
            }
         }
      
         // Check the calcode_id to see if it's Creation Wizard or Modification Notebook:
         if( calCodeId == null || trim(calCodeId) == ''){
            if(parent.getPanelAttribute("CustomizedPromotionPWPType","hasTab")=="NO") {
               parent.setPanelAccess("CustomizedPromotionPWPType", true);
               parent.setPanelAttribute( "CustomizedPromotionPWPType", "hasTab", "YES" );
               parent.setPanelAccess("RLProdPromoWhat", true);
               parent.setPanelAttribute( "RLProdPromoWhat", "hasTab", "YES" );
               parent.TABS.location.reload();
               parent.setPreviousPanel("RLProdPromoWhat");
            }
            // disable the range panel since we don't use it in this PWP sample.
            parent.setPanelAttribute( "RLProdPromoWizardRanges", "hasTab", "NO" );
            parent.TABS.location.reload();         
         } 
         else {
            parent.setPanelAttribute( "CustomizedPromotionPWPType", "hasTab", "YES" );
            parent.TABS.location.reload();   
         }
       
         if (parent.get) {
            var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
            var hasMax = false;
            if (o != null) {
               // ### initialize all the form elements by existing object:
               with (document.customizedPWPForm) { 
                  promotedSku.value = o.<%= RLConstants.RLPROMOTION_DISCOUNT_ITEM_SKU %>;
                  fixedCost.value = o.<%= ExtendedPromotionConstants.PROMOTION_FIXCOST %>;
                  if(!isNaN(o.<%= ExtendedPromotionConstants.PROMOTION_PROMOTEDITEMQTY %>))
                  promotedQty.value = o.<%= ExtendedPromotionConstants.PROMOTION_PROMOTEDITEMQTY %>;
                  if(o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %> == '1' || 
            o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %> == null || 
                     o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %> == "")
                     {
                        hasMax = false;
                        maxRad[0].checked=true;
                  }else {
                     if(!isNaN(o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %>)) {
                        hasMax = true;
                        maxRad[1].checked=true;
                        minProdPurchaseQty.value = o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %>;
                     } 
                  }
                  checkMaxArea(hasMax);
               }
            }  
         }
       
         if (trim(cType) != '' && trim(cId) != '') {
            if (parent.get) {
               var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
               if (o != null) {
                  o.<%= ExtendedPromotionConstants.PROMOTION_PROMOTEDPRODUCTID %> = "<%=UIUtil.toJavaScript(catEntryId)%>";
               }
            }
            if (calCodeId == null || trim(calCodeId) == '') {
               parent.finish();
            } 
            else {
               if (nextPanel == null || trim(nextPanel) == '' || nextPanel == 'undefined') {
                  parent.finish();
               }
               else {
                  parent.gotoPanel(nextPanel);     
               }
            }
         }
         else {
            if ( (!needValidation && trim(discountSku) != '') && (trim(cType) == null || trim(cType) == '' || trim(cId) == '' || trim(cId) == null)) {
               needValidation = true;
               alertDialog("<%= UIUtil.toJavaScript(RLPromotionNLS.get("RLInvalidItemSKU").toString())%>");      
            }
            else if (needValidation && trim(discountSku) != '') {
               alertDialog("<%= UIUtil.toJavaScript(RLPromotionNLS.get("RLInvalidSKU").toString())%>");
            }
         }
      
         parent.setContentFrameLoaded(true);
      
         if (parent.get("prodQtyTooLong", false)) {
            parent.remove("prodQtyTooLong");
            reprompt(document.customizedPWPForm.promotedQty,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodQtyNumberTooLong").toString())%>");
            return;
         }
         if (parent.get("prodQtyNotNumber", false)) {
            parent.remove("prodQtyNotNumber");
            reprompt(document.customizedPWPForm.promotedQty,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodMinNotNumber").toString())%>");
            return;
         }
         if (parent.get("reqProdQtyTooLong", false)) {
            parent.remove("reqProdQtyTooLong");
            reprompt(document.customizedPWPForm.minProdPurchaseQty,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodQtyNumberTooLong").toString())%>");
            return;
         }
         if (parent.get("reqProdQtyNotNumber", false)) {
            parent.remove("reqProdQtyNotNumber");
            reprompt(document.customizedPWPForm.minProdPurchaseQty,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodMinNotNumber").toString())%>");
            return;
         }
         if (parent.get("noSKUEntered", false)) {
            parent.remove("noSKUEntered");
            alertDialog("<%= UIUtil.toJavaScript(RLPromotionNLS.get("SKUNotEntered").toString())%>");
            return;
         }
      }
      function savePanelData() {
         if (parent.get) {
            var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
            if (o != null) {
               with (document.customizedPWPForm) {
                  o.<%= RLConstants.RLPROMOTION_DISCOUNT_ITEM_SKU %> = promotedSku.value;
                  o.<%= ExtendedPromotionConstants.PROMOTION_FIXCOST %> = fixedCost.value;
                  if (trim(promotedQty.value) != null && trim(promotedQty.value) != "") {
                     o.<%= ExtendedPromotionConstants.PROMOTION_PROMOTEDITEMQTY %> = parent.strToNumber(trim(promotedQty.value),"<%=fLanguageId%>");
                  } 
                  else {
                     o.<%= ExtendedPromotionConstants.PROMOTION_PROMOTEDITEMQTY %> = "";
                  } 
         if (maxRad[1].checked) {
                     if (trim(minProdPurchaseQty.value) != null && trim(minProdPurchaseQty.value) != "")
                     o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %> = parent.strToNumber(trim(minProdPurchaseQty.value),"<%=fLanguageId%>");
                     else
                     o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %> = "";
                  }
         else {
                     o.<%= ExtendedPromotionConstants.PROMOTION_REQUIREDITEMQTY %> = 1;
                  }
                  if(o.<%= RLConstants.RLPROMOTION_CATENTRY_TYPE %> == 'ProductBean')
                     o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= ExtendedPromotionConstants.PROMOTION_PRODUCTLEVELPWP %>";
                  else if(o.<%= RLConstants.RLPROMOTION_CATENTRY_TYPE %> == 'ItemBean')
           o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= ExtendedPromotionConstants.PROMOTION_ITEMLEVELPWP %>";
                  else if(o.<%= RLConstants.RLPROMOTION_CATENTRY_TYPE %> == 'PackageBean')
           o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= ExtendedPromotionConstants.PROMOTION_ITEMLEVELPWP %>";
                  else if(o.<%= RLConstants.RLPROMOTION_CATENTRY_TYPE %> == 'Category')
           o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>";
               }
            }
         }
         return true;
      }
      
      function validatePanelData() {
         with (document.customizedPWPForm) {
            if(trim(promotedSku.value) == "" || trim(promotedSku.value) == null) {
               alertDialog("<%= UIUtil.toJavaScript(RLPromotionNLS.get("SKUNotEntered").toString())%>");
               return false;
            }
            if(trim(fixedCost.value) == "" || trim(fixedCost.value) == null) {
               alertDialog("The item cost for this promotion is required.");
               return false;
            } 
            else if (!validateCost(fixedCost)) return false;
      
            if (!validateQty(promotedQty)) return false;
            if (maxRad[1].checked) {
               if (!validateQty(minProdPurchaseQty)) return false;
            }
         }
         if (needValidation) {
            this.location.replace("/webapp/wcs/tools/servlet/CustomizedPromotionPWPView?discountSKU=" + document.customizedPWPForm.promotedSku.value);
            return false; // this will force to stay in the same panel
         } else {
            return true; // go to next panel
         } 
      }
      
      function validateNoteBookPanel(gotoPanelName){
         with (document.customizedPWPForm) {
            if(trim(promotedSku.value) == "" || trim(promotedSku.value) == null) {
               alertDialog("<%= UIUtil.toJavaScript(RLPromotionNLS.get("SKUNotEntered").toString())%>");
               return false;
            } 
            if(trim(fixedCost.value) == "" || trim(fixedCost.value) == null) {
               alertDialog("The item cost for this promotion is required.");
               return false;
            } 
            else if (!validateCost(fixedCost)) return false;
      
            if (!validateQty(promotedQty)) return false;
            if (maxRad[1].checked) {
               if (!validateQty(minProdPurchaseQty)) return false;
            }
         }
         if (needValidation) {
            this.location.replace("/webapp/wcs/tools/servlet/CustomizedPromotionPWPView?discountSKU=" + document.customizedPWPForm.promotedSku.value + "&calCodeId=" + calCodeId + "&gotoPanelName="+ gotoPanelName);
            return false; // this will force to stay in the same panel
         } else {
            return true; // go to next panel
         } 
      }
      
      function validateCost(cost) {
         if ( !parent.isValidInteger(trim(cost.value), "<%=fLanguageId%>")) || 
               (!parent.isValidDouble(trim(cost.value), "<%=fLanguageId%>")))  {
            reprompt(cost,"The fixed cost value is not a number.");
            return false;
         } 
         else if (!(eval(parent.strToNumber(trim(cost.value),"<%=fLanguageId%>")) >= 0)) {
            reprompt(cost,"The fixed cost has to be greater than or equals to zero.");
            return false;
         }
         return true;
      }
      
      function validateQty(qtyField) {
         if(parent.strToNumber(trim(qtyField.value),"<%=fLanguageId%>").toString().length > 14) {
            reprompt(qtyField,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodQtyNumberTooLong").toString())%>");
            return false;
         }
         else if ( !parent.isValidInteger(trim(qtyField.value), "<%=fLanguageId%>")) {
            reprompt(qtyField,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodMinNotNumber").toString())%>");
            return false;
         }
         else if (!(eval(parent.strToNumber(trim(qtyField.value),"<%=fLanguageId%>")) >= 1)) {
            reprompt(qtyField,"<%= UIUtil.toJavaScript(RLPromotionNLS.get("prodMinNotNumber").toString())%>");
            return false;
         }
         return true; 
      }
      
      function checkMaxArea(hasMax) {
         if (hasMax) {
            document.all["maxArea"].style.display = "block";
         }
         else {
            document.all["maxArea"].style.display = "none";
         }
      }
      
      function callSearch() {
         var rlpagename = "CustomizedPromotionPWP";
         savePanelData();
         var productSKU = document.customizedPWPForm.promotedSku.value;
         top.saveModel(parent.model);
         top.saveData(parent.pageArray, "RLPWPPageArray");
         if (parent.get) {
            var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
            if (o != null) {   
               top.saveData(o,"RLPromotion");
            }
         }
         top.put("inputsku",productSKU);
         // This variable will and value will be used to distinguish which search 
         // function the search page should provide.
         top.put("<%= RLConstants.RLPROMOTION_PROD_SEARCH_PAGE %>",rlpagename);
         top.saveData(productSKU, "inputsku");
         top.setReturningPanel("CustomizedPromotionPWPType");
         top.setContent("<%= RLPromotionNLS.get("ProductSearchBrowserTitle") %>","/webapp/wcs/tools/servlet/RLSearchDialogView?ActionXMLFile=RLPromotion.RLSearchDialog",true);
      }
      
      function setValidationFlag() {
         if(trim(document.customizedPWPForm.promotedSku.value) != '') {
            needValidation = true;
         }
      }
      
      </script>
      <meta name="GENERATOR" content="IBM WebSphere Studio" />
      </head>
      
      <body class="content" onload="initializeState();">
      
      <form name="customizedPWPForm" id="customizedPWPForm">
      
      <h1>Purchase With Purchase</h1>
      <br />
      
      <label for="promotedSkuLabel">Product SKU selected as promoted (required)</label><br />
      <table border="0" cellpadding="0" cellspacing="0" id="customizedPWP_Table_1">
      <tr>
      <td id="customizedPWP_TableCell_1"> 
        <input name="promotedSku" type="text" size="15" maxlength="50" onchange="setValidationFlag()" id="promotedSkuLabel" /> 
      </td>
      <td id="customizedPWP_TableCell_2"> 
        <button type="button" value='Find' name="rlSearchProduct" class="enabled" style="width:auto;text-align:center" onclick="javascript:callSearch();"> Find</button> 
      </td>
      </tr>
      </table>
      
      
      <p><label for="promotedQtyLabel">Number of items to be given for a fixed cost based on purchase (required)</label><br />
      <input name="promotedQty" type="text" size="10" maxlength="14" id="promotedQtyLabel" />
          Items</p>
      <p><label for="fixCostLabel">Fixed cost for each unit of promoted items (required)</label><br />
      <input name="fixedCost" type="text" size="10" maxlength="14" id="fixCostLabel" />
      <script language="JavaScript">
      document.write(parent.getCurrency());
      </script></p>
      
      Minimum qualification<br />
      <input type="radio" name="maxRad" onclick="javascript:checkMaxArea(false);" checked ="checked" id="None" /><label for="None">None</label><br />
      <input type="radio" name="maxRad" onclick="javascript:checkMaxArea(true);" id="MinQty" /><label for="MinQty">Specify a minimum qualification for the promotion</label>
      <div id="maxArea" style="display:none">
       <blockquote>
       <p><label for="purchaseAmount">Required quantity</label><br />
       <input name="minProdPurchaseQty" type="text" size="10" maxlength="14" id="purchaseAmount" />
          Items
       </p>   
       </blockquote>
      </div>
      
      </form>
      </body>
      </html>
      
    8. Save the file, but do not close the development environment.
  9. Create a view which corresponds to the new page in the Struts configuration file to map the new JSP file to the corresponding view. Add the entries to the struts-config-ext.xml file to point to your custom JSP page. All customization changes should be made in struts-config-ext.xml, not to struts-config.xml. To register the new view, do the following:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/WEB-INF folder.
    2. From the struts-config-ext.xml file's pop-up menu, select Open.
    3. Add the following code:
    
    <global-forwards>
       <forward name="CustomizedPromotionPWPView" path="/tools/custom/CustomizedPWP.jsp" 
          className="com.ibm.commerce.struts.ECActionForward" /> 
    </global-forwards>
    <!-- Action Mappings --> 
    <action-mappings type="com.ibm.commerce.struts.ECActionMapping">
       <action path="/CustomizedPromotionPWPView"
          type="com.ibm.commerce.struts.BaseAction" />
    </action-mappings>
    
  10. Update the wizard and notebook XML files to recognize the new promotion type. To protect your customized data, it must be created in a safe place, separate from the WebSphere Commerce assets. This procedure creates new Promotions wizard and notebook XML definition files, in a new directory. This is not done within the development environment. To add the button:

    1. Copy the WCDE_installdir/xml/tools/epromotion/RLPromotionWizard.xml file, and move the file to the WCDE_installdir/xml/myCustomXML/tools/epromotion directory.
    2. Navigate to the WCDE_installdir/xml/myCustomXML/tools/epromotion directory.
    3. Open the new RLPromotionWizard.xml file in an editor.
    4. Add the following code into the new RLPromotionWizard.xml immediately after the RLDiscountGWPType panel section:
      
      <panel name="CustomizedPromotionPWPType"
         url="CustomizedPromotionPWPView"
         helpKey=""
         hasTab="NO"
         hasNext="NO" 
         hasFinish="YES" />
      
    5. Save and close the file.
    6. Open the RLPromotionNotebook.xml file, that was previously modified in step 6, in an editor.
    7. Add the following code into the new RLPromotionNotebook immediately after the RLDiscountGWPType panel section:
      
        <panel name="CustomizedPromotionPWPType"
         url="CustomizedPromotionPWPView"
         helpKey=""
         hasTab="NO" />
      
    8. Save and close the file.
  11. Register the new Java object type for use by the promotion factory. To update the registry, add the following code into the WC_installdir/xml/tools/epromotion/RLPromotion.xml file.
    
      <type name="CategoryLevelPurchaseWithPurchase"
         className="com.myCompany.promotions.implementations.CategoryLevelPurchaseWithPurchase"
         mappingFileRelativePath="" />
    
  12. Modify the new CustomizedPWP.jsp to properly handle the wizard and notebook panel flow.
    1. Add the following import statement to the top of your JSP to include your extended data bean object:
      <%@ page import="com.ibm.commerce.tools.epromotion.ExtendedPromotionConstants" %>
    2. Create a form to display and process the purchase condition elements.
    3. Modify the following methods:
      • initializeState(),
      • savePanelData(),
      • validatePanelData(),
      • validateNoteBookPanel().
      Also, update any related methods as necessary.
    4. If your promotion type requires it, update the validateAllPanels method in the following file: Web Content/javascript/tools/epromotion/rlDiscountNotebook.js
    5. Save the file, but do not close the development environment.
  13. Update the RLPromotionProperties.jsp to support the new promotion type and view. To update this JSP file:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/tools/epromotion Right-click on the RLPromotionProperties.jsp file, and select Copy.
    2. Right-click on the Web Content/tools/custom folder, and select Paste.
    3. Navigate to the Web Content/tools/custom folder.
    4. Double-click the new file to open it in an editor.
    5. Since the custom file is in a different location, you have to update the following path in the JSP to reflect the original location. Search for the following include statement:
      
      <%@include file="epromotionCommon.jsp" %>
      

      and change it to:

      
      <%@include file="../epromotion/epromotionCommon.jsp" %>
      
    6. Also, you must add an include statement to import the custom logic. Add the following to the include block:
      
      <%@include file="com.myCompany.epromotion.logic.*" %>
      
    7. Update the panel initialization in the top section of the JSP. Locate the following code:
      
               } else if (o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_ITEMLEVELBUYXGETYFREE %>" 
                      || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>"
                      || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_CATEGORYLEVELBUYXGETYFREE %>"){
                   parent.setPanelAttribute( "RLProdPromoWhat", "hasTab", "YES");
                   parent.setPanelAttribute( "RLProdPromoGWPType", "hasTab", "YES" );
                   parent.reloadFrames();
               }
            }
            else {
               parent.setPanelAttribute( "RLProdPromoWizardRanges", "hasTab", "NO" );
               parent.setPanelAttribute( "RLDiscountWizardRanges", "hasTab", "NO" );
               parent.reloadFrames();
            }
         }
      }
      

      and update it to match the following:

      
               } else if (o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_ITEMLEVELBUYXGETYFREE %>"
                      || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>"
                      || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_CATEGORYLEVELBUYXGETYFREE %>"){
                    parent.setPanelAttribute( "RLProdPromoWhat", "hasTab", "YES");
                    parent.setPanelAttribute( "RLProdPromoGWPType", "hasTab", "YES" );
                    parent.reloadFrames();
               } else if (o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= ExtendedPromotionConstants.PROMOTION_ITEMLEVELPWP %>" 
                      || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= ExtendedPromotionConstants.PROMOTION_PRODUCTLEVELPWP %>" 
                      || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>"){
                    parent.setPanelAttribute( "RLProdPromoWhat", "hasTab", "YES");
                    parent.setPanelAttribute( "CustomizedPromotionPWPType", "hasTab", "YES" );
                    parent.reloadFrames();
               }
            }
            else {
               parent.setPanelAttribute( "RLProdPromoWizardRanges", "hasTab", "NO" );
               parent.setPanelAttribute( "RLDiscountWizardRanges", "hasTab", "NO" );
               parent.reloadFrames();
            }
         }
      }
      
    8. Update the initializePromotionType function to add new promotion type into drop-down list. The updated function should match the following code, though some lines are broken here for display purposes:
      
      function initializePromotionType() {
         document.propertiesForm.rlPromotionType.options[0] = new Option("<%=RLPromotionNLS.get("percentOffPerItem")%>", 
            "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERCENTDISCOUNT %>", true, true);
         document.propertiesForm.rlPromotionType.options[1] = new Option("<%=RLPromotionNLS.get("fixedAmountOffPerItem")%>", 
            "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERITEMVALUEDISCOUNT %>", false, false);
         document.propertiesForm.rlPromotionType.options[2] = new Option("<%=RLPromotionNLS.get("fixedAmountOffAll")%>", 
            "<%= RLConstants.RLPROMOTION_PRODUCTLEVELVALUEDISCOUNT %>", false, false);
         document.propertiesForm.rlPromotionType.options[3] = new Option("<%=RLPromotionNLS.get("buyXGetY")%>",  
            "<%= RLConstants.RLPROMOTION_ITEMLEVELSAMEITEMPERCENTDISCOUNT %>", false, false);
         document.propertiesForm.rlPromotionType.options[4] = new Option("<%=RLPromotionNLS.get("freeGiftWithPurchase")%>", 
            "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>", false, false);
         document.propertiesForm.rlPromotionType.options[5] = new Option("Purchase with Purchase",
            "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>", false, false);
      }
      
    9. Update the initializeState function to initialize the new promotion type. Locate the following code:
      
       
      } else if (o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_ITEMLEVELBUYXGETYFREE %>" 
            || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>" 
            || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_CATEGORYLEVELBUYXGETYFREE %>"){
         document.propertiesForm.rlPromotionGroup.options[0].selected = true;
         refreshPromoTypeAndCombinationFields(document.propertiesForm.rlPromotionGroup); 
         document.propertiesForm.rlPromotionType.options[4].selected = true;
         lastGroup = 0;
         lastType = 4;
      }
      

      Immediately after this code, insert the following code:

      
       
      else if (o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>" 
            || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= ExtendedPromotionConstants.PROMOTION_PRODUCTLEVELPWP %>" 
            || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= ExtendedPromotionConstants.PROMOTION_ITEMLEVELPWP %>"){
         document.propertiesForm.rlPromotionGroup.options[0].selected = true;
         refreshPromoTypeAndCombinationFields(document.propertiesForm.rlPromotionGroup);       
         document.propertiesForm.rlPromotionType.options[5].selected = true;
         lastGroup = 0;
         lastType = 5;
      }
      
    10. Update the refreshPromoTypeAndCombinationFields(promGroup) function for the new type. The updated function should match the following code, though some lines are broken here for display purposes. The updated code is highlighted in bold. Also, note that this is only a partial sample, which illustrates the beginning of the function, but includes all necessary changes:
      function refreshPromoTypeAndCombinationFields(promGroup) {
         var length = document.propertiesForm.rlPromotionType.options.length;
         for(var i=length-1; i>=0; i--) {
            document.propertiesForm.rlPromotionType.options[i] = null;
         }
      
         var length = document.propertiesForm.inCombination.options.length;
         for(var i=length-1; i>=0; i--) {
            document.propertiesForm.inCombination.options[i] = null;
         }
      
         if(promGroup.options[0].selected) {
            document.propertiesForm.rlPromotionType.options[0] = new Option("<%=RLPromotionNLS.get("percentOffPerItem")%>", 
                  "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERCENTDISCOUNT %>", true, true);
            document.propertiesForm.rlPromotionType.options[1] = new Option("<%=RLPromotionNLS.get("fixedAmountOffPerItem")%>", 
                  "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERITEMVALUEDISCOUNT %>", false, false);
            document.propertiesForm.rlPromotionType.options[2] = new Option("<%=RLPromotionNLS.get("fixedAmountOffAll")%>", 
                  "<%= RLConstants.RLPROMOTION_PRODUCTLEVELVALUEDISCOUNT %>", false, false);
            document.propertiesForm.rlPromotionType.options[3] = new Option("<%=RLPromotionNLS.get("buyXGetY")%>",  
                  "<%= RLConstants.RLPROMOTION_ITEMLEVELSAMEITEMPERCENTDISCOUNT %>", false, false);
            document.propertiesForm.rlPromotionType.options[4] = new Option("<%=RLPromotionNLS.get("freeGiftWithPurchase")%>", 
                  "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>", false, false);
            document.propertiesForm.rlPromotionType.options[5] = new Option("Purchase with Purchase", 
                  "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>", false, false);
        .
        .
        .
      
    11. Save the file, but do not close the development environment.
  14. Update RLProdPromoWhat.jsp to handle the wizard flow and new promotion target properly. To update this JSP file:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/tools/epromotion folder. Right-click on the RLProdPromoWhat.jsp file, and select Copy.
    2. Right-click on the Web Content/tools/custom folder, and select Paste.
    3. Navigate to the Web Content/tools/custom folder.
    4. Double-click the new file to open it in an editor.
    5. Since the custom file is in a different location, you have to update the following path in the JSP to reflect the original location. Search for the following include statement:
      
      <%@include file="epromotionCommon.jsp" %>
      

      and change it to:

      
      <%@include file="../epromotion/epromotionCommon.jsp" %>
      
    6. Also, you must add an include statement to import the custom logic. Add the following to the include block:
      
      <%@include file="com.myCompany.epromotion.logic.*" %>
      
    7. Update the initializeState function, which requires multiple additions:
      1. This is the beginning of the updated function. Note that this is only a partial code sample, which illustrates the beginning of the function, but includes all necessary changes, highlighted in bold:
        
        function initializeState() {
           var fromSearchPage = top.get("fromSearchPage");
           // If the user has returned from search page., get RLPromotion object 
           // from top and put it in parent
           if(fromSearchPage != 'undefined' && fromSearchPage == true) {
              top.put("fromSearchPage", false);
              var rlPromo = top.getData("RLPromotion");
              parent.put("<%= RLConstants.RLPROMOTION %>", rlPromo);   
           }
          
           var obj = parent.get("<%= RLConstants.RLPROMOTION %>", null); // Get the object from parent
           if (obj!= null)  { 
              calCodeId = obj.<%= RLConstants.EC_CALCODE_ID %>;
              // merchanType is using to store the type of targeted items: Category, ProductBean, ItemBean
              merchanType = obj.<%= RLConstants.RLPROMOTION_MERCHANDISE_TYPE %>;      
           }
           else {   
              merchanType = top.get("<%= RLConstants.RLPROMOTION_MERCHANDISE_TYPE%>"); 
           }
           if(parent.getPanelAttribute( "RLProdPromoWizardRanges", "hasTab") == "NO") {
              parent.setPanelAttribute( "RLProdPromoWhat", "hasTab", "YES" );
              parent.TABS.location.reload();
           }
            
           if ((merchanType != null) && (merchanType != '')) {
              // if Category already been selected before, do following initilzation:
              if(isPWPType()) {
                 merchanType = 'Category';
              }
              if(merchanType =='Category')
        .
        .
        . 
        
      2. This section disables item and product selection for promotions of the new type. Note that this is only a partial code sample. All changes are highlighted in bold:
        
        .
        .
        .
        if(catList != null && catList.length > 0) {
           document.whatForm.merchandise[2].checked=true;
           document.whatForm.merchandise[2].focus();
           document.all["categorySelect"].style.display = "block";
           if (calCodeId != null && trim(calCodeId) != '' ) {
              document.whatForm.merchandise[0].disabled=true;
              document.whatForm.merchandise[1].disabled=true;
           }
           if (isPWPType()) {
              document.whatForm.merchandise[0].disabled=true;
              document.whatForm.merchandise[1].disabled=true;
           }
           
           for (var i=0; i<catList.length; i++) {
              var nextOptionIndex = document.whatForm.rlCgryList.options.length;
              var createOption = true;
              for (var j=0; j<badCgryArray.length; j++) {
                 if (catList[i] != 'undefined') {
                    if(catList[i] == badCgryArray[j]){
                       createOption = false; 
                       break;
                    }
                 }
              }
              if (createOption) {
                 document.whatForm.rlCgryList.options[nextOptionIndex] = new Option(
                 catList[i], // name
                 catList[i], // value
                 false,    // defaultSelected
                 false);   // selected 
              }
           }
        }
        else {
           document.whatForm.merchandise[2].checked=true;
           document.whatForm.merchandise[2].focus();
           document.all["categorySelect"].style.display = "block";
           if (isPWPType()) {
              document.whatForm.merchandise[0].disabled=true;
              document.whatForm.merchandise[1].disabled=true;
           }
        }
        document.whatForm.rlCgryName.value = "";
        .
        .
        .
        
      3. Finally, there are a two more places that require customization, near the end of the initializeState function. Note that this is only a partial code sample, but includes all necessary changes, highlighted in bold. The code snippet should be included at the end of the function:
        
           .
           .
           .
           else {
              if (parent.get) {
                 var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
                 if ((o != null) && (o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>")) {
                    document.whatForm.merchandise[2].checked=true;
                    document.whatForm.merchandise[2].focus();
                    showCategorySelectFields();
                    document.whatForm.merchandise[0].disabled=true;
                    document.whatForm.merchandise[1].disabled=true;
                 }   
                 else // In creation when the page is loaded first time, select Product promotion radio button by default
                    {
                    document.whatForm.merchandise[0].checked=true;
                    document.whatForm.merchandise[0].focus();
                    showProductSelectFields(); 
                 }
              }
              else {
                 document.whatForm.merchandise[0].checked=true;
                 document.whatForm.merchandise[0].focus();
                 showProductSelectFields(); 
              }
           }
         
           // initialize the notebook panel:  
              if (parent.get) {
              var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
              if (o != null) {
                 calCodeId = o.<%= RLConstants.EC_CALCODE_ID %>;
                 whichDiscountType = o.<%= RLConstants.RLPROMOTION_TYPE %>;
                 if( calCodeId != null && calCodeId != '') {
                    if( whichDiscountType != null || whichDiscountType != '') {
                       var pgArray = top.getData("RLProdPromoWhatPageArray");
                       if(pgArray != null) {
                          parent.pageArray = pgArray;
                       }
                       if (whichDiscountType == "<%= RLConstants.RLPROMOTION_ITEMLEVELPERCENTDISCOUNT %>" 
                             || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERCENTDISCOUNT %>" 
                             || whichDiscountType == "<%= RLConstants.RLPROMOTION_CATEGORYLEVELPERCENTDISCOUNT %>"
                             || whichDiscountType == "<%= RLConstants.RLPROMOTION_ITEMLEVELPERITEMVALUEDISCOUNT %>" 
                             || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERITEMVALUEDISCOUNT %>" 
                             || whichDiscountType == "<%=  RLConstants.RLPROMOTION_CATEGORYLEVELPERITEMVALUEDISCOUNT  %>"
                             || whichDiscountType == "<%= RLConstants.RLPROMOTION_ITEMLEVELVALUEDISCOUNT %>" 
                             || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELVALUEDISCOUNT %>" 
                             || whichDiscountType == "<%=  RLConstants.RLPROMOTION_CATEGORYLEVELVALUEDISCOUNT %>"){
                          parent.setPanelAttribute( "RLProdPromoWizardRanges", "hasTab", "YES" );
                          parent.TABS.location.reload();
                       }else if (whichDiscountType == "<%= RLConstants.RLPROMOTION_ITEMLEVELSAMEITEMPERCENTDISCOUNT %>" 
                             || whichDiscountType == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELSAMEITEMPERCENTDISCOUNT %>" 
                             || whichDiscountType == "<%=  RLConstants.RLPROMOTION_CATEGORYLEVELSAMEITEMPERCENTDISCOUNT %>"){
                          parent.setPanelAttribute( "RLProdPromoBXGYType", "hasTab", "YES" );
                          parent.TABS.location.reload();
                       }else if (whichDiscountType == "<%= RLConstants.RLPROMOTION_ITEMLEVELBUYXGETYFREE %>" 
                             || o.<%= RLConstants.RLPROMOTION_TYPE %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>" 
                             || whichDiscountType == "<%=  RLConstants.RLPROMOTION_CATEGORYLEVELBUYXGETYFREE %>"){
                          parent.setPanelAttribute( "RLProdPromoGWPType", "hasTab", "YES" );
                          parent.TABS.location.reload();
                       }
                       else if (whichDiscountType == "<%= ExtendedPromotionConstants.PROMOTION_ITEMLEVELPWP %>" 
                             || whichDiscountType == "<%= ExtendedPromotionConstants.PROMOTION_PRODUCTLEVELPWP %>" 
                             || whichDiscountType == "<%=  ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>"){
                          parent.setPanelAttribute( "CustomizedPromotionPWPType", "hasTab", "YES" );
                          parent.TABS.location.reload();
                       }
                    }  //end of if whichDiscountType.
                 }  // end of calcode!=null
              } // end if o!=null
           } // end if parent.get
        
           parent.setContentFrameLoaded(true);
        
           if (parent.get("CategoryNotEntered", false)) {
              parent.remove("CategoryNotEntered");
              alertDialog('<%= UIUtil.toJavaScript(RLPromotionNLS.get("CategoryNotEntered").toString())%>');
              return;
           }
        
           if (parent.get("PurchaseSKUNotEntered", false)) {
              parent.remove("PurchaseSKUNotEntered");
              alertDialog('<%= UIUtil.toJavaScript(RLPromotionNLS.get("PurchaseSKUNotEntered").toString())%>');
              return;
           }
           
           if (parent.get("cannotBeAProduct", false)) {
              parent.remove("cannotBeAProduct");
              alertDialog('<%= UIUtil.toJavaScript(RLPromotionNLS.get("cannotBeAProduct").toString())%>');
              return;
           }
        
        } // end Initialize state
        
    8. Update the savePanelData function to add the new promotion type, so that it properly manages the panel flow. The updated function should match the following code, though some lines are broken here for display purposes. Also, note that this is only a partial sample, but includes all necessary changes. The code snippet should be included at the end of the function:
      
            .
            .
            .
                  o.<%= RLConstants.RLPROMOTION_CATGROUP_CODE %> = tempCgryList;     
                  if (calCodeId == null || trim(calCodeId) == '') { 
                     var ranges = o.<%= RLConstants.RLPROMOTION_RANGES %>;
                     var values = o.<%= RLConstants.RLPROMOTION_VALUES %>;
                     if(ranges.length > 2 || (ranges.length == 2 && eval(values[0]) != 0) ) {
                        parent.setNextBranch("RLProdPromoWizardRanges");
                     }
                     else if(o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERCENTDISCOUNT %>") {
                        o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= RLConstants.RLPROMOTION_CATEGORYLEVELPERCENTDISCOUNT %>";
                        parent.setNextBranch("RLProdPromoPercentType");
                     }
                     else if(o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELPERITEMVALUEDISCOUNT %>") {
                        o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= RLConstants.RLPROMOTION_CATEGORYLEVELPERITEMVALUEDISCOUNT %>";
                        parent.setNextBranch("RLProdPromoFixedType");
                     }
                     else if(o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELVALUEDISCOUNT %>") {
                        o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= RLConstants.RLPROMOTION_CATEGORYLEVELVALUEDISCOUNT %>";
                        parent.setNextBranch("RLProdPromoFixedType");
                     }
                     else if(o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= RLConstants.RLPROMOTION_ITEMLEVELSAMEITEMPERCENTDISCOUNT %>") {
                        o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= RLConstants.RLPROMOTION_ITEMLEVELSAMEITEMPERCENTDISCOUNT %>";
                        parent.setNextBranch("RLProdPromoBXGYType");
                     }
                     else if(o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= RLConstants.RLPROMOTION_PRODUCTLEVELBUYXGETYFREE %>") {
                        o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= RLConstants.RLPROMOTION_CATEGORYLEVELBUYXGETYFREE %>";
                        parent.setNextBranch("RLProdPromoGWPType");
                     }
                     else if(o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>") {
                        o.<%= RLConstants.RLPROMOTION_TYPE %> = "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>";
                        parent.setNextBranch("CustomizedPromotionPWPType");
                     }
                  } 
               }
               o.<%= RLConstants.RLPROMOTION_CATENTRY_TYPE %> = trim(skuType );  
            }
         }
      }  
      
    9. Create an isPWPType function. To add this function, insert the following code:
      
      function isPWPType() {
         if (parent.get) {    
            var o = parent.get("<%= RLConstants.RLPROMOTION %>", null);
            if ((o != null) && (o.<%= RLConstants.RLPRODPROMO_TYPEALIAS %> == 
                  "<%= ExtendedPromotionConstants.PROMOTION_CATEGORYLEVELPWP %>")) {
               return true;
            }
         else {
               return false;
            }
         }
         else {
            return false;
         }
      }
      
    10. Save the file, but do not close the development environment.
  15. Create a label for the wizard panel. WebSphere Commerce design separates program logic from displayed strings, so labels for buttons, and all other interface elements are stored in properties files, which are also referred to as resource bundles. To protect your customized data from being overwritten during migration to a future version, or during installation of a future fix pack, or some similar event, it must be created in a safe place, separate from the WebSphere Commerce assets. This procedure creates a new properties file, in a new folder, within the WebSphere Commerce development environment:
    1. In the WebSphere Commerce development environment, navigate to the WebSphereCommerceServerExtensionsLogic project.
    2. Navigate to the src folder.
    3. Right-click on the src folder, and select New, and then Package.
    4. In the Name Field on the New Java Package dialog, enter a unique name. For the purposes of this scenario, enter com.myCompany.epromotion.properties, where myCompany represents some custom directory name. Packages created by IBM, and included in WebSphere Commerce follow a naming convention which begins, "com.ibm..."
    5. Click Finish to create the package.
    6. Right-click on the com.myCompany.epromotion.properties folder, and select New, and then Other. In the New dialog, click Simple, File, and then Next.
    7. In the File name field, enter a unique name. For the purposes of this scenario, enter RLPromotionNLS_locale.properties, where locale represents the locale from which your business users will access the WebSphere Commerce Accelerator.
    8. Click Finish to create the file and open it in an editor.
    9. To simplify future maintenance, it is a good idea to include comments in this new file. This is optional, though strongly recommended. At the top of this file, include some comments similar to the following to clarify this file's purpose:
      
      #
      # Customized properties for Promotions
      #
      # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      
    10. Scroll down and create a "Button definitions" section for the file, and insert a label for your new button. This should look similar to the following sample:
      
      #
      # Wizard labels
      #
      
      CustomizedPromotionPWPType=Purchase Condition
      
    11. Save the file, but do not close the development environment.

    This addresses the label in the language for the locale you specified when you created the file. If you wanted to enter a string in more than one language, you would have to perform this step for each language, creating a file for each corresponding locale.

    If a business user attempts to access this page using a locale for which there is no explicit support, the tools framework will default to the RLPromotionNLS.properties file, which will not contain a corresponding string. You should ensure that you create a version of the properties file for every locale from which you expect users to access it.

  16. When you create a custom properties file, you must update the appropriate resources.xml file so that the new file is available to the tools within the component. This is not done within the development environment. To make this update:
    1. Copy the WCDE_installdir/xml/tools/epromotion/resources.xml file, and move the file to the WCDE_installdir/xml/myCustomXML/tools/epromotion/ directory.
    2. Open the new resources.xml file in an editor.
    3. Scroll down to the "Resource bundle" section of the file, and update the code so that it matches the following sample:
      
      <resourceBundle name="RLPromotionNLS">
          <bundle>com.ibm.commerce.tools.campaigns.properties.RLPromotionNLS</bundle>
          <bundle>com.myCompany.epromotions.properties.RLPromotionNLS</bundle> 
      </resourceBundle>
      
      where myCompany represents your custom directory name.
    4. Save the file.

    Note that the original properties file is located in the tools.campaigns.properties package. This is an result of a development limitation in a prior version, and is not an error.

  17. Update the RLProductSearchResult.jsp to handle the new promotion type. To update this JSP file:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/tools/epromotion folder. Right-click on the RLProductSearchResult.jsp file, and select Copy.
    2. Right-click on the Web Content/tools/custom folder, and select Paste.
    3. Navigate to the Web Content/tools/custom folder.
    4. Double-click the new file to open it in an editor.
    5. Since the custom file is in a different location, you have to update the following path in the JSP to reflect the original location. Search for the following include statement:
      
      <%@include file="epromotionCommon.jsp" %>
      

      and change it to:

      
      <%@include file="../epromotion/epromotionCommon.jsp" %>
      
    6. Also, you must add an include statement to import the custom logic. Add the following to the include block:
      
      <%@ page import="com.myCompany.epromotion.logic.ExtendedPromotionConstants" %>
      
    7. Update the initialization to handle the new promotion type. This code defines the catalog assets included in the search, as items, products, or packages. Note that this is only a partial code sample, which illustrates the beginning of the initialization function, but includes all necessary changes, highlighted in bold:
      
        <body onload="onLoad();" class="content_list">
        
        <%
         catEntSearchListBean.setCommandContext(commandContext);
         try {
            // Execute the search bean
            if (fromPage.equals("RLProdPromoWhat")) {
               if (promoType.equalsIgnoreCase("ProductBean")) {
                  catEntSearchListBean.setIsItem(false);
                  catEntSearchListBean.setIsPackage(false);
                  catEntSearchListBean.setIsProduct(true);
                  catEntSearchListBean.setIsDynamicKit(false);
                  catEntSearchListBean.setIsBundle(false);            
               }  
               else if (promoType.equalsIgnoreCase("ItemBean") || promoType.equalsIgnoreCase("PackageBean")) {
                  catEntSearchListBean.setIsItem(true);
                  catEntSearchListBean.setIsPackage(true); // don't care whether item or package. consider it as item
                  catEntSearchListBean.setIsProduct(false);
                  catEntSearchListBean.setIsDynamicKit(false);
                  catEntSearchListBean.setIsBundle(false);
               }
            }
            else if ( fromPage.equals("RLProdPromoGWP") || fromPage.equals("RLDiscountGWP") ) {
               catEntSearchListBean.setIsItem(true);
               catEntSearchListBean.setIsPackage(false);
               catEntSearchListBean.setIsProduct(false);
               catEntSearchListBean.setIsDynamicKit(false);
               catEntSearchListBean.setIsBundle(false);
            }
            else if ( fromPage.equals("CustomizedPromotionPWP")) {
               catEntSearchListBean.setIsItem(true);
               catEntSearchListBean.setIsPackage(true);
               catEntSearchListBean.setIsProduct(true);
               catEntSearchListBean.setIsDynamicKit(false);
               catEntSearchListBean.setIsBundle(false);
            }
            .
            .
            .
      
    8. Update the myRefreshButtons function. The updated function should match the following code, though some lines are broken here for display purposes.
      
      function myRefreshButtons() {
         var fromPage = '<%=fromPage%>';
         var checked = parent.getChecked();
         if(checked.length > 1 && (fromPage == "RLProdPromoGWP" || fromPage == "RLDiscountGWP")) {   
            if(defined(parent.buttons.buttonForm.ButtonAddButton)) {
               parent.buttons.buttonForm.ButtonAddButton.disabled=false;
               parent.buttons.buttonForm.ButtonAddButton.className='disabled';
               parent.buttons.buttonForm.ButtonAddButton.id='disabled';
            }
         }
         if(checked.length > 1 && (fromPage == "CustomizedPromotionPWP")) {   
            if(defined(parent.buttons.buttonForm.ButtonAddButton)) {
               parent.buttons.buttonForm.ButtonAddButton.disabled=false;
               parent.buttons.buttonForm.ButtonAddButton.className='disabled';
               parent.buttons.buttonForm.ButtonAddButton.id='disabled';
            }
         }  
      }
      
    9. Update the addAction function for page return. Note that this is only a partial code sample, which illustrates the end of the addAction function, but includes all necessary changes, highlighted in bold:
      
           .
           .
           .
         else if (rlpagename == "RLDiscountGWP") {
            var calCodeId = null;
            var rlPromo = top.getData("RLPromotion", 1);
            if (rlPromo != null) {
               rlPromo.<%= RLConstants.RLPROMOTION_DISCOUNT_ITEM_SKU %> =  partNumber[0];
               rlPromo.<%= RLConstants.RLPROMOTION_GWP_CATENTRY_ID %> = catEntryID[0];
               calCodeId = rlPromo.<%= RLConstants.EC_CALCODE_ID %>;
               top.sendBackData(rlPromo,"RLPromotion");
            }
      
            if( (calCodeId == null) || (calCodeId == '') ) {
               top.goBack();
            }else  {
               top.goBack();
            }
         }
         else if (rlpagename == "CustomizedPromotionPWP") {
            var calCodeId = null;
            var rlPromo = top.getData("RLPromotion", 1);
            if (rlPromo != null) {
               rlPromo.<%= RLConstants.RLPROMOTION_DISCOUNT_ITEM_SKU %> = partNumber[0];
               rlPromo.<%= ExtendedPromotionConstants.PROMOTION_PROMOTEDPRODUCTID %> = catEntryID[0];
               calCodeId = rlPromo.<%= RLConstants.EC_CALCODE_ID %>;
               top.sendBackData(rlPromo,"RLPromotion");
            }
            if( (calCodeId == null) || (calCodeId == '') ) {
               top.goBack();               
            }
            else {
               top.goBack();                
            }
         }
      }
      
    10. Save the file, but do not close the development environment.
  18. Update the RLDiscountDetails.jsp to handle the new promotion type. To update this JSP file:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/tools/epromotion folder. Right-click on the RLDiscountDetails.jsp file, and select Copy.
    2. Right-click on the Web Content/tools/custom folder, and select Paste.
    3. Navigate to the Web Content/tools/custom folder.
    4. Double-click the new file to open it in an editor.
    5. Since the custom file is in a different location, you have to update the following path in the JSP to reflect the original location. Search for the following include statement:
      
      <%@include file="epromotionCommon.jsp" %>
      

      and change it to:

      
      <%@include file="../epromotion/epromotionCommon.jsp" %>
      
    6. Also, you must add an include statement to import the custom logic. Add the following to the include block:
      
      <%@ page import="com.myCompany.epromotion.implementations.*" %>
      
    7. Update the writeSpecificData function. Note that this is only a partial code sample, which illustrates the required changes to the writeSpecificData function. The code snippet should be included at the end of the function:
      
           .
           .
           .
        <%
         if (rlDiscountDetails.getRLPromotionType().equalsIgnoreCase("CategoryLevelPurchaseWithPurchase")){
            if(rlDiscountDetails.getRLPromotion() instanceof CategoryLevelPurchaseWithPurchase) {
               CategoryLevelPurchaseWithPurchase obj = (CategoryLevelPurchaseWithPurchase) rlDiscountDetails.getRLPromotion();
         Vector cgryEntries = null;
         String promotedSkuString = "";
         String cgryString ="";
         String cgryName ="";  
         if (obj != null) {
                  cgryEntries = obj.getCatgpIdentifiers();   
         String catid = obj.getPromotedProductId();
         promotedSkuString = util.getSKU(catid);   
         %>       
                  document.write('<p><%=UIUtil.toJavaScript(RLPromotionNLS.get("RLCategoryDetails").toString())%> ' + '<br>');
                  <%= UIUtil.toJS("categories", cgryEntries)%>
         <%  
                  for (int i=0; i<cgryEntries.size(); i++) {
                     cgryString = cgryEntries.elementAt(i).toString();
                     cgryName = util.getCategoryName(storeId, langId, cgryString);
         %>     
                     document.write('<i><%=UIUtil.toJavaScript(cgryString ).toString()%></i>');
                     document.write(' (<i><%= UIUtil.toJavaScript(cgryName ).toString()%></i>)');  
                     document.write("<br>");
         <%
               } 
         %> 
                  document.write("</p>");      
         <%
                  if(obj.getRequiredItemQty() == -1 ) {   
         %>
                     document.write('<p>');
                     document.write('<i>Get '+'<%=obj.getPromotedItemQty()%>'+' items of '+'<%=promotedSkuString %>'+' at ' + 
                           '<%=obj.getCurrency()%> <%=obj.getFixCost()%>' + ' with any purchase of required category items.</i>');
                     document.write("</p>");      
         <%
                  }
                  else {
         %>
                     document.write('<p>');
                     document.write('<i>Get '+'<%=obj.getPromotedItemQty()%>'+' items of '+'<%=promotedSkuString %>'+' at ' + 
                           '<%=obj.getCurrency()%> <%=obj.getFixCost()%>' + ' with a minimum purchase of '+ '<%=obj.getRequiredItemQty()%>' + 
                           ' required category items.</i>');
                     document.write("</p>");      
         <%
                  }
               } // end of if (obj != null)
            } 
         }
         %>
      
    8. Save the file, but do not close the development environment.
  19. To display the promotion description for a particular product, item, or category in the store's home page without using a campaign initiative. you have to populate 2 relationship tables CATENCALCD or CATGPCALCD. For example, promotion A (calcode 1001) applies when a customer buys something from category B (catalogGroupId 200), to give a reward of product C (catentryId 888) at 50% off the regular price. This relationship between calcode 1001 and catalogGroupId 200 has to be defined in the CATGPCALCD table, and the relationship between calcode 1001 and catentryId 888 has to be defined in the CATENCALCD table. To provide support in the WebSphere Commerce Accelerator for this relationship definition, do the following steps:
    1. Extend the current CreateRLPromotionCmdImpl command:
      
      public class CustomCreateRLPromotionCmdImpl extends 
          com.ibm.commerce.tools.epromotion.commands.CreateRLPromotionCmdImpl {
      
         /**
          * CustomCreateDiscountCmdImpl constructor.
          */    public CustomCreateRLPromotionCmdImpl() {
                    super();
        
      
      
         /**
          * Create catEntCalcode.
          * @exception com.ibm.commerce.exception.ECSystemException
          *
          */
          public void createCatEntCalCodeBean() throws ECSystemException
          {
              super();
                  if(rlPromotion.getRLPromotionType().equals(RLPROMOTION_CATEGORYLEVELPWP)) {
                  // RLPROMOTION_CATEGORYLEVELPWP is the custom type
                  // registered in the RLPromotion.xml.
                  // Another way to do this if custom type extends from RLItemLevelPromotion type :
                  // if (rlPromotion instanceof RLItemLevelPromotion) {
                  // Populate the CATENCALCD table for all of the products in this 
                  // promotion and calcode. If this type is related to a category, 
                  // then populate the table CATGPCALCD as well.
              }
          }
      
          public void performExecute() throws ECSystemException, ECException {
          try {
              super();
              createCatEntCalCodeBean();
          }   catch (ECException ex)
              {
              throw new ECSystemException(ECMessage._ERR_GENERIC, getClass().getName(),
                     methodName, ex);
              }
              catch (Exception ex)
              {
              throw new ECSystemException(ECMessage._ERR_GENERIC, getClass().getName(), 
                     methodName, ex);
              }}
      }
      
      
      
       
      
    2. Similarly, you must create a custom command to update existing promotions of the new type using the WebSphere Commerce Accelerator. To provide this support extend the current UpdateRLPromotionCmdImpl command:
      
      public class CustomUpdateRLPromotionCmdImpl extends 
         com.ibm.commerce.tools.epromotion.commands.UpdateRLPromotionCmdImpl {
            public CustomUpdateRLPromotionCmdImpl() {
               super();
            }
      
            public void performExecute() throws com.ibm.commerce.exception.ECException 
            {         
               try {            
                  super();
                  this.updateCatEntCalCode();
                  this.updateCatGpCalCode();
               }            
               catch (Exception e) {
                  throw new ECSystemException(ECMessage._ERR_GENERIC, getClass().getName(), 
                     methodName, e);
               }
            }
      
            public void updateCatEntCalCode() throws ECSystemException 
            {     // Remove the old entries, save the updated entries.
                  // Refer to the createCatEntCalCodeBean() method in 
                  // the CustomCreateRLPromotionCmdImpl command.
            }
      
            public void updateCatGpCalCode() throws ECSystemException 
            {     // Remove the old entries, save the updated entries.
                  // Refer to the createCatEntCalCodeBean() method in 
                  // the CustomCreateRLPromotionCmdImpl command.
            }
      }
      
  20. Update the views that correspond to the customized pages in the Struts configuration file to map the new JSP files to their corresponding view. The commands must be overwritten in the configuration file. Add the entries to the struts-config-ext.xml file to point to your customized JSP page. All customization changes should be made in struts-config-ext.xml, not to struts-config.xml. To register the new views, do the following:
    1. In the WebSphere Commerce development environment, navigate to the CommerceAccelerator/Web Content/WEB-INF folder.
    2. From the struts-config-ext.xml file's pop-up menu, select Open.
    3. Add the following code:
    
    <global-forwards>
       <forward name="RLPromotionPropertiesView" path="/tools/custom/RLPromotionProperties.jsp" 
          className="com.ibm.commerce.struts.ECActionForward" /> 
       <forward name="RLProdPromoWhatView" path="/tools/custom/RLProdPromoWhat.jsp" 
          className="com.ibm.commerce.struts.ECActionForward" /> 
       <forward name="RLPromotionProdSearchResultView" path="/tools/custom/RLProductSearchResult.jsp" 
          className="com.ibm.commerce.struts.ECActionForward" /> 
       <forward name="RLDiscountDetailsDialogView" path="/tools/custom/RLDiscountDetails.jsp" 
          className="com.ibm.commerce.struts.ECActionForward" /> 
    </global-forwards>
    <!-- Action Mappings --> 
    <action-mappings type="com.ibm.commerce.struts.ECActionMapping">
       <action path="/RLPromotionPropertiesView"
          type="com.ibm.commerce.struts.BaseAction" /> 
       <action path="/RLProdPromoWhatView" 
          type="com.ibm.commerce.struts.BaseAction" /> 
       <action path="/RLPromotionProdSearchResultView"
          type="com.ibm.commerce.struts.BaseAction" /> 
       <action path="/RLDiscountDetailsDialogView" 
          type="com.ibm.commerce.struts.BaseAction" /> 
    </action-mappings>
    
  21. Update the CMDREG table in the WebSphere Commerce command registry to associate the new task command implementations with the existing controller command interface. The following SQL statement shows an example update:
    
    update CMDREG set CLASSNAME='CustomCreateRLPromotionCmdImpl' where 
        INTERFACENAME='com.ibm.commerce.tools.epromotion.commands.CreateRLPromotionCmd'
    
    
    update CMDREG set CLASSNAME='CustomUpdateRLPromotionCmdImpl' where 
        INTERFACENAME='com.ibm.commerce.tools.epromotion.commands.UpdateRLPromotionCmd'
    
  22. Test your customization in your development environment. To complete this test:
    1. Stop and restart your development WebSphere Commerce instance. Refer to the for details about how to stop and restart this instance.
    2. Launch the WebSphere Commerce Accelerator.
    3. From the Marketing menu, select Promotions.
    4. Click New Promotion. This launches the Promotion wizard, which should include the new discount type. Create a promotion using the new type.
    5. Browse the store using a test customer ID. Compile a shopping cart that satisfies the criteria for the new promotion, and determine whether the promotion is being correctly applied to the order. If this works, then the customization has been a success, and you can proceed to propagate all of the changes you made to the development environment to the production environment as detailed in the following steps. If this fails, you will have to determine the cause of the error, and debug.
  23. Export the updated assets. To export the assets, right-click the new CustomizedPWP.jsp file and select Export. The Export Wizard opens. In the Export wizard:
    1. Select File system and click Next.
    2. The CustomizedPWP.jsp is selected.
    3. Ensure that you also select the following files:
      • RLPromotionProperties.jsp
      • RLProdPromoWhat.jsp
      • RLProductSearchResult.jsp
      • RLDiscountDetails.jsp
    4. Navigate to the WebSphereCommerceServerExtensionsLogic/src/com/myCompany/epromotion/logic folder, and select the ExtendedPromotionConstants file.
    5. Navigate to the WebSphereCommerceServerExtensionsLogic/src/com/myCompany/epromotion/implementations folder, and select the CategoryLevelPurchaseWithPurchase file.
    6. Navigate to the WebSphereCommerceServerExtensionsLogic/src/com/myCompany/epromotion/databeans folder, and select the RLProductDisountDatabean file.
    7. Navigate to the WebSphereCommerceServerExtensionsLogic/src/com/myCompany/epromotion/properties folder, and select the RLPromotionsNLS_locale.properties file.
    8. Select Create directory structure for selected files.
    9. In the To directory field, enter a temporary directory into which these resources will be placed. For example, enter C:\ExportTemp\StoreAssets
    10. Click Finish.
    11. If prompted, create the specified directory.
  24. Transfer the updated assets to the production environment. To transfer the files:
    1. On the target machine, locate the WebSphere Commerce instance .ear directory. The following is an example of this directory:
      • For IBM i OS operating system /QIBM/ProdData/WebAsAdv4/installedApps/ WAS_node_name/ WC_instance_name.ear
      • SolarisLinuxAIX /WAS_installdir/installedApps/cellName/ WC_instance_name.ear
      • Windows/WAS_installdir\installedApps\cellName\WC_ instance_name.ear Where:
      instance_name
      The name of your WebSphere Commerce instance.
      For IBM i OS operating system WAS_node_name
      For IBM i OS operating systemThe system where the WebSphere Application Server product is installed.
      cellName
      The WebSphere Application Server cell name.
    2. Copy the files exported in step 22, above, into the following directories:
      CustomizedPWP.jsp
      WC_eardir/CommerceAccelerator.war/tools/custom
      RLPromotionProperties.jsp
      WC_eardir/CommerceAccelerator.war/tools/custom
      RLProdPromoWhat.jsp
      WC_eardir/CommerceAccelerator.war/tools/custom
      RLProductSearchResult.jsp
      WC_eardir/CommerceAccelerator.war/tools/custom
      RLDiscountDetails.jsp
      WC_eardir/CommerceAccelerator.war/tools/custom
      myCampaignsRB_locale.properties
      WC_eardir/properties/com/myCompany/epromotion/properties
    3. Transfer the following files to your production environment:
      /xml/myCustomXML/tools/epromotion/RLPromotionWizard.xml
      Copy to AppServer/V6/Base/profiles/instance_name/installedApps/WC_instance_name_ce ll/WC_instance_name.ear/xml/myCustomXML/tools/epromotion/RLPromotionWizard.xml
      /xml/myCustomXML/tools/epromotion/RLPromotionNotebook.xml
      Copy to AppServer/V6/Base/profiles/instance_name/installedApps/WC_instance_name_ce ll/WC_instance_name.ear/xml/myCustomXML/tools/epromotion/RLPromotionNotebook.xml
      /xml/myCustomXML/tools/epromotion/resources.xml
      Copy to AppServer/V6/Base/profiles/instance_name/installedApps/WC_instance_name_ce ll/WC_instance_name.ear/xml/myCustomXML/tools/epromotion/resources.xml
    4. Replicate the changes made in step 11 to the run time version of the RLPromotion.xml file. This file can be found in the following directory:

      AppServer/V6/Base/profiles/instance_name/installedApps/WC_instance_name_ce ll/WC_instance_name.ear/xml/tools/epromotion/

      The required change is to add the following block of code to register the new Java object type for use by the promotion factory.
      <type name="CategoryLevelPurchaseWithPurchase" 
         className="com.myCompany.promotions.implementations.CategoryLevelPurchaseWithPur
      chase" 
         mappingFileRelativePath=""/>
      
    5. Update the WebSphere Commerce configuration file.
  25. Package and deploy the updated assets.
  26. Restart your WebSphere Commerce instance.