Thursday, June 18, 2009

ATG Add Custom properties to SKU

Extending Commerce item

In some situations we have to add some additional properties to existing commerce object (commerceItem) and we need to integrate it with ATG commerce.

For eg in configuring a ring we may have to add some additional properties to ring added in Cart.

Say a ring may need some additional properties like Metal Finish, Stone Color, and Engraving etc. In effect this means we should be able to “Configure the ring”.

In configuring a window blinds we need to pass some additional properties to window blinds commerceitem

Like
Room name : (Any user input value)
Color: (User select from a set of options provided)
Height: (User select from a set of options provided)
Width: (User select from a set of options provided)
Etc….

There is a chance that some of these properties are not added to the commerce item while adding to cart because user may not need that. So the question one having in mind are

1) Do we need to extend the commerceItem to include each and every additional properties?.
2) Can we do this Dynamically at run time?.

Modifying commerce Item for each and every properties will be cumbersome as sonner or later there is a chance of additional properties or some properties are going to filter out. So we can simplify this situation by considering a MAP. i.e java.util.Map
Which is having key and values.

So we have to extend commerceItem to reflect the Map. And all additional properties names with values can be added into the Map as key value pairs while adding to Shopping cart.

This indeed solves the multiplicity problem of properties.But how we add the map to OOTB commerceitem? IS there any complexities while adding the modified/extended commerceitem to order/cart?. If the properties need repricing how we add that?

One of the Easiest ways to handle this situation is by modifying Purchase Process. This includes a sequence of steps.




Step 1 of 7:Extend class CommerceItemImpl

First we need to extend the commerce object hierarchy by subclassing an existing object and adding new primitive data type properties. When we add primitive data type properties, you don't need to write any code to save the properties to or load the properties from the Order Repository.Here we are adding a Map object. Which is not primitive datatype So we have to add some code to reflect the props in order.

import atg.commerce.order.CommerceItemImpl;


public class BlindsCommerceItemImpl extends CommerceItemImpl {

static final long serialVersionUID = 1;


public BlindsCommerceItemImpl() {
      super();
}


public Map getSelectedOptions()
{
       return (Map) getPropertyValue("selectedOption");
}


public void setSelectedOptions(Map selectedOptions)
{
      setPropertyValue("selectedOption", selectedOptions);
}


public double getBlindsPrice() {
      return ((Double) getPropertyValue("designedPrice")).doubleValue();
}


public void setBlindsPrice(double blindsPrice) {
      setPropertyValue("designedPrice", new Double(blindsPrice));
}

}



We have added 2 properties here
1)getters and setters for selected Options.
This holds the Map property.

2) getters and setters for blindsPrice.
This is a value holder for Price. Which we will discuss later.


In the code example above, note the calls to getPropertyValue() and setPropertyValue(). These methods retrieve and set the values of properties directly on the repository item objects; they are part of the atg.commerce.order.ChangedProperties interface. In a commerce object that supports the ChangedProperties interface, every get() method needs to call the getPropertyValue() method, and similarly every set() method needs to call the setPropertyValue() method.


Step 2 of 7- Extend the Order Repository Definition File
Extend the Order Repository definition file, orderrepository.xml, to add the new properties in MyCommerceItemImpl to the existing commerceItem item descriptor. In this example, the new property to add is the shortDescription property.
The orderrepository.xml file is found in the CONFIGPATH at /atg/commerce/order/orderrepository.xml. To extend the file, create a new orderrepository.xml file at /atg/commerce/order/ in your localconfig directory. The new file should define the shortDescription property for the commerceItem item descriptor. During startup, the ATG platform uses XML file combination to combine the orderrepository.xml files in the CONFIGPATH into a single composite XML file.



We have added new table called BLINDS_ITEM_FEATURES which is of type multi to hold the MAP. And also added a transient property “designedPrice” to hold the custom price.


We created the new myCommerceItem item descriptor, defining both its properties which is a map and the database table and columns that store those properties. Now we need to modify accordingly the Order Repository database schema.
The following DDL statement creates the database table and columns specified in the orderrepository.xml file that you created in step 2.

The database script is

CREATE TABLE BLINDS_ITEM_FEATURES
(
COMMERCE_ITEM_ID VARCHAR2(40 BYTE) NOT NULL,
FEATURE_ID VARCHAR2(254 BYTE),
OPTION_ID VARCHAR2(254 BYTE)
)
LOGGING
NOCOMPRESS
NOCACHE
NOPARALLEL
MONITORING;


ALTER TABLE BLINDS_ITEM_FEATURES ADD (
PRIMARY KEY
(COMMERCE_ITEM_ID, FEATURE_ID));


ALTER TABLE BLINDS_ITEM_FEATURES ADD (
FOREIGN KEY (COMMERCE_ITEM_ID)
REFERENCES DCSPP_ITEM (COMMERCE_ITEM_ID));


Step 3 of 7 - Modify the OrderTools Configuration File
The OrderTools component controls many aspects of the purchase process, such as mapping between commerce object types and class names, defining the default commerce object types, and mapping between commerce objects and item descriptors. You need to modify the OrderTools configuration file to support the new MyCommerceItemImpl class and myCommerceItem item descriptor.
To modify the OrderTools configuration file, layer on a configuration file by creating an OrderTools.properties file at /atg/commerce/order/ in your localconfig directory.
Add new entry to beanNameToItemDescriptorMap which declares our extended Commerce item.

beanNameToItemDescriptorMap=\

atg.projects.b2cblueprint.order.BlueprintOrderImpl=order,\

atg.commerce.order.CommerceItemImpl=icommerceItem,\

blinds.commerce.order.BlindsCommerceItemImpl=commerceItem,\

Also edit the commerceItemTypeClassMap to add our commerce item as default entry unless we don’t specify this in CartFormHandler

commerceItemTypeClassMap+=\

giftWrapCommerceItem=atg.projects.b2cblueprint.order.GiftWrapCommerceItem,\

default=blinds.commerce.order.BlindsCommerceItemImpl




Step 4 of 7 – Extend CommerceItemInfo class

This is a info class which acts like a property holder which hold the latest info about the commerce item configured which we are going to add to cart. So the values we are going to pass to the commerce items are 1) the Map 2) The Price So the calss will looks like

import atg.commerce.order.purchase.AddCommerceItemInfo;



public class BlindsAddCommerceItemInfo extends AddCommerceItemInfo

{


public BlindsAddCommerceItemInfo() {

      super();


}



private Map selectedOptions;

private double blindsPrice;



public double getBlindsPrice() {

       return blindsPrice;

}
 


public void setBlindsPrice(double blindsPrice) {

      this.blindsPrice = blindsPrice;

}
 


public Map getSelectedOptions() {

      return selectedOptions;

}
 


public void setSelectedOptions(Map selectedOptions) {

      this.selectedOptions = selectedOptions;

}


}




Step 5 of 7 – Extend PurchaseProcessHelper class

Extend Purchase Process Helper Class
Override the method setCommerceItemProperties . Here we transfer the properties form AddCommerceItemInfo class to actual CommerceItem.

import atg.commerce.order.purchase.PurchaseProcessHelper;


public class BlindsPurchaseProcessHelper extends PurchaseProcessHelper {



public BlindsPurchaseProcessHelper() {

      super();


}

}




protected void setCommerceItemProperties(CommerceItem pItem,

AddCommerceItemInfo pItemInfo) throws CommerceException {


     super.setCommerceItemProperties(pItem, pItemInfo);

     if (!(pItem instanceof BlindsCommerceItemImpl)) {

     throw new CommerceException(

                           "CommerceItem not an instance of BlindsCommerceItemImpl");

                         }

      if (!(pItemInfo instanceof BlindsAddCommerceItemInfo)) {

     throw new CommerceException(

                           "AddCommerceItemInfo not an instance of BlindsAddCommerceItemInfo");

}
 


if (isLoggingDebug()) {

              logDebug("INSIDE BlindsPurchaseProcessHelper.setCommerceItemProperties");


 


BlindsCommerceItemImpl blindsItem = (BlindsCommerceItemImpl) pItem;


BlindsAddCommerceItemInfo blindsInfo = (BlindsAddCommerceItemInfo) pItemInfo;

if (isLoggingDebug()) {

      logDebug(" BLinds info from purchase process=="+blindsInfo);

}




if (isLoggingDebug()) {

         logDebug("The Map got==="+blindsInfo.getSelectedOptions());

}


blindsItem.setSelectedOptions(blindsInfo.getSelectedOptions());


}

}
Make this a component by adding PurchaseProcess.properties in the path atg/commerce/order/Purchase. Note that the path is very important else ATG will take the default one.

Step 6 of 7-Extend CartFormHandler

In Blinds.com the CartFormHandler Extends the default CartModifierFormHandler for custom functionality.
This class holds all the handle methods for the buttons on the cart
page. Since all buttons need to perform similar functionality,
including updating item quantities, adding gift wrap/gift message
and moving to the checkout process, all the methods have been
captured in this class. In the case of ExpressCheckout, this
class does the preliminary duties of modifying cart contents, and
then calls the ExpressCheckoutFormHandler to run the express
checkout pipeline.


import atg.commerce.order.purchase.CartModifierFormHandler;

public class BlindsCartModifierFormHandler extends CartModifierFormHandler {



private Map featureOptionMap;



public void setFeatureOptionMap(Map featureOptionMap)

{

           this.featureOptionMap = featureOptionMap;

}

public Map getFeatureOptionMap()

{

           return featureOptionMap;

}



protected void addItemToOrder(DynamoHttpServletRequest pRequest,

DynamoHttpServletResponse pResponse)

throws ServletException, IOException

{


if (isLoggingDebug()) {

         logDebug("Start BlindsCartModifierFormHandler addItemToOrder");

}





try {

          boolean valid = mergeItemInputForAdd(pRequest, pResponse,

          getCommerceItemType(), false);

          if (valid) {



          if (isLoggingDebug()) {

                    logDebug("VALID adding item and    commerceItemType=="+getCommerceItemType());

             }



Object someObj = getItems()[0];



if (isLoggingDebug()) {

               logDebug("Cast it to BlindsAddCommerdeItemInfo");

}




BlindsAddCommerceItemInfo itemInfo = (BlindsAddCommerceItemInfo) getItems()[getItems().length-1];



if (isLoggingDebug()) {

       logDebug("After Cast");

}

//Get the Map and set to AddItemInfo



if (isLoggingDebug()) {

        logDebug(" Feature option map from CartFormHandler = "+getFeatureOptionMap());

}


itemInfo.setSelectedOptions(getFeatureOptionMap());


double price = getBlindsPrice(getFeatureOptionMap(), itemInfo.getCatalogRefId());

itemInfo.setBlindsPrice(price);



if (isLoggingDebug()) {

           logDebug("Done adding item item info from cart Form Handler=="+itemInfo);

}



}

}catch (Exception re) {

if (isLoggingDebug()) {

logDebug("Error in addItemToOrder" + re.getMessage());

}



re.printStackTrace();

}


super.addItemToOrder(pRequest, pResponse);




// super.addItemToOrder(pRequest,pResponse);

if(getFormError())

{

           //mark the transaction for roll back

           TransactionManager tm = getTransactionManager();

           if(tm != null)

           {

               try

                    {

                        Transaction t = tm.getTransaction();

                         if(t != null)

                                      t.setRollbackOnly();

                    }

                  catch (SystemException exc) {

                         if (isLoggingError())

l                                     ogError(exc);

                   }



}

}

}





}



In the Jsp page Point all the feature and option to featureOptionMap.
In the properties(CartFormHandler.properties)) file add an entry to specify which is the CommerceItemInfo class we are using

addItemInfoClass=blinds.commerce.order.BlindsAddCommerceItemInfo

Also note that we are setting the 2 items into BLindsAddCommerceItemInfo Class
1)The FeatureOption which is the MAP.
2)The Price calculated




Step 7 of 7- Create pre-Calculator for pricing


ATG Commerce provides several preconfigured pricing calculators. We can extend these calculators to fit your site's requirements, and you can also create new pricing calculators if necessary.

We are extending atg.commerce.pricing.ItemPricingCalculator .

package blinds.commerce.pricing.calculators;



import atg.commerce.pricing.priceLists.ItemPriceCalculator;

import atg.commerce.pricing.ItemPriceInfo;

import atg.commerce.pricing.PricingException;





public class BlindsItemListPriceCalculator extends ItemListPriceCalculator {



public static final String PAYMENT_TYPE = "paymentType";



public static final String CREDIT = "1";







protected void priceItem(double pPrice, ItemPriceInfo pPriceQuote, CommerceItem pItem, RepositoryItem pPricingModel, Locale pLocale, RepositoryItem pProfile, Map pExtraParameters) throws PricingException {




BlindsCommerceItemImpl blindsItem = (BlindsCommerceItemImpl)pItem;

pPrice = blindsItem.getBlindsPrice();

super.priceItem(pPrice, pPriceQuote, pItem, pPricingModel, pLocale, pProfile, pExtraParameters);



} // end priceItem



}

Code blindsItem.getBlindsPrice(); will retrieve the price from the commerceitem and will pass it to pricingEngine through the method priceItem();

Make the calculator a component by adding the ItemListPriceCalculator.properties in the location atg/commerce/pricing/calculators/.

After you have created a new calculator, you must associate it with its corresponding pricing engine. You can do this in either by Adding the calculator to an engine's list of pre- or post-calculators. The configuration invokes the calculator on the price of every item that passes through the engine.

So Create the ItemPricingEngine.properties inside atg/commerce/pricing
Add the line calculator

1 comment:

Anonymous said...

Hi, can you add an example or tutorial for update each item current Shopping Cart with ShoppingCartModifier.setOrderByRelationshipId