Sunteți pe pagina 1din 20

ORACLE 11i Advanced Pricing QP: Leveraging Oracle Configurator and Attribute Mapping

An Oracle White Paper March 2003

ORACLE 11i Advanced Pricing : Leveraging Oracle Configurator and Attribute Mapping

EXECUTIVE OVERVIEW

Oracle Advanced Pricing 11.5.8 adds functionality that greatly simplifies the process of defining additional pricing attributes. Setup screens have been consolidated, and it is no longer necessary to define flex fields and defaulting rules. This paper is offered as an update and supplement to the Oracle Advanced Pricing Implementation manual Release 11i and the White Paper QP: Dont Customize, Extend. In this paper, you will learn how to define a new pricing context, define new pricing attributes that source data from Oracle Configurator tables, and use those attribute values in Price List Lines and Formulas. The pricing structure will work exactly the same in Order Management, Quoting, and iStore demonstrating a single point of setup for multiple selling channels.
INTRODUCTION

This white paper provides a solution for how to price items based on attributes chosen at runtime in Oracle Configurator. In this example, the item is a service where pricing logic is to be applied based on non-BOM options selected for a given product. While the attributes herein were mapped from the Oracle Configurator tables, they could have come from any source, and the process would remain essentially the same.

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 2

SUPPORTING DATA

Pricing rule data: Server A Platinum Gold - 24/7 onsite - 2hr onsite response Silver - 24/7 online& phone - 24/7 onsite - 2hr onsite response Bronze - 24/7 online& phone 3324 2220 Gold+20% Gold+30% 1080 Silver+20% Silver+30% Silver+50% 800 Brnz+20% Server B 5916 3372 Gold+20% Gold+30% 1836 Silver+20% Silver+30% Silver+50% 1200 Brnz+20% Storage 1 6060 4056 Gold+20% Gold+30% 1968 Silver+20% Silver+30% Silver+50% 1300 Brnz+20% Storage 2 3264 1980 Gold+20% Gold+30% 1212 Silver+20% Silver+30% Silver+50% 900 Brnz+20%

The above rates are for a 1 year service agreement. In addition, there is a DURATION factor to apply a 1.90 multiplier for 2 years, and a 2.72 multiplier for 3 years. To summarize, prices are defined for each combination of Product and Metal Level, then an adjustment is applied based on the Options selected, Duration, Geography, and Customer.

ORACLES APPOACH

With this scenario, it is possible to set up 16 part numbers, 1 for each combination of product and metal level. However, in this example we chose to do everything with ONE part number instead. While there are certain advantages using 16 part number method (easier visibility in Price List Maintenance Form), we wanted to prove the point that we dont have to have a proliferation of part numbers in order to have dynamic pricing.

In this example, servers and storage units are highly configurable, so Oracle Configurator is part of the solution footprint. The attributes by which prices are determined are easy to collect within a Configurator user interface. The only thing left was to tell Advanced Pricing how to find them. The latest Attribute Mapping capabilities make this easy. Given the above, our solution breaks down into the following steps:

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 3

Add the appropriate questions to the Configurator UI (Simple Oracle Configurator Development) Format the answers to these questions in the CZ CONFIG ATTRIBUTES table for ease of extraction (a single Oracle Configurator Functional Companion Rule) Create a single PL/SQL Attribute Mapping function which handles variable inputs Define the new pricing attributes in advanced pricing Use the attributes in price list lines, formulae, and modifiers as needed

Configurator Model Structure

We start with a single part number called SUPREME. This service could be applied to one or many servers and or storage units that are sold as a part of a system configuration. As such, the part number was added to the BOM of each server and each storage unit. The result is that, for a given system configuration, the SUPREME would appear multiple times, at various levels in the structure. The level at which SUPREME appears gives us the PRODUCT attribute. The answers to various questions in the configuration UI give us METAL LEVEL and OPTIONS, and potentially GEOGRAPHY (not modeled in this scenario). The CUSTOMER is a normal, readily available attribute of the ordering process.
Configurator Functional Companion to build CZ CONFIG ATTRIBUTES

While it is possible for the pricing engine to source the attributes directly from the normal CZ schema, there are advantages in efficiency and ease of extraction to putting the attributes into a formatted table within the CZ schema. The concept of Configurator Output Attributes can extend far beyond pricing attributes to include descriptive or instructive data for down stream processing, workflow triggers, etc. A straightforward Functional Companion (FC) rule (a java extension) was written and attached to the model structure. (Java code is attached in Appendix A) The effect of this FC is to write a row into the standard Oracle table CZ_CONFIG_ATTRIBUTES for each occurrence of the part number SUPREME in the final, CONFIGURED Bill of Material. In the columns of each row are the values for the attributes PRODUCT, METAL LEVEL, OPTION, DURATION.

Functional Companions are a non-invasive, upgrade resistant method of adding to the functionality of the out of the box Oracle Configurator. THIS IS AN EXTENSION, NOT A CUSTOMIZATION
Create a single PL/SQL Attribute Mapping function

A new function was written and installed in the instance that will retrieve the values from the CZ_CONFIG_ATTRIBUTES table. A single function was written, that handles variable inputs, and can thus be reused to retrieve various values. (PL/SQL body of the function is attached as Appendix B). It is likely that it could be achieved in a more generic fashion to maximize re-use, given a broader perspective in an overall implementation. The key point is that this function can be defined such that it never needs to be maintained again. All pricing behaviors based on the results of this function are defined in Oracle forms. There has been no change made to standard code, so future upgrades should have no impact. THIS IS AN EXTENSION, NOT A CUSTOMIZATION.

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 4

Define the new pricing attributes in advanced pricing

First we set up a new CONTEXT: PRICING > SETUP > CONTEXT AND ATTRIBUTES The context in this case was named SM-2. From within this same screen, you simply add the new attributes, and save it.

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 5

Next you LINK the attributes PRICING > SETUP > ATTRIBUTE LINKING AND MAPPING

Choose your new context, and click the LINK ATTRIBUTES BUTTON

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 6

For each of our new attributes, we have chosen ATTRIBUTE MAPPING as the Mapping method. Other options here include CUSTOM SOURCED which is a call to an external program, or USER ENTERED, which can be used to generate flex fields that can be populated on an Order Management line, for example. Now we will drill into the mapping for the METAL LEVEL:

What we see above is that there is a separate map for ASO, OKC, and ONT. We have selected a User Source Type of PL/SQL API. Other alternatives include CONSTANT, SYSTEM VARIABLE, PROFILE OPTION, and PL/SQL API Multi-Record. The key to the map is the User Value String. This could simply be an attribute that exists in the associated Global Object, in this case ASO_PRICING_INT.G_LINE_REC, or, it could be a call to a package that you create, using one of the values in the Global Object as a hook to get somewhere else. In the above user value string, we have said: Run the function Get_SM_Price_Attr from within package SM_PRICING, the inputs included the strings ASO METAL-LEVEL and SMQPValue, as well as the config

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 7

header, revision, and quote line code which are natural elements of the Global Object ASO_PRICING_INT.G_LINE_REC. Below is an example of how this string looks when we are calling from ORDER MANAGEMENT, looking for the DURATION attribute.

As you can see from the string above, we have substituted ASO for ONT, and DURATION for METAL-LEVEL. Also, since Order Management uses a different Global Object (OE_ORDER_PUB.G_LINE), and the Config header and revision and line have different names in that object, the variables are changed accordingly. THE PL/SQL PACKAGE AND FUNCTION ARE THE SAME! Now we can use these attribute values wherever we like!

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 8

Use the attributes in price list lines, formulae, and modifiers as need

Since the prices were defined for each combination of PRODUCT and METAL LEVEL, we set up a price list line for each:

The pricing engine, based on the combination PRODUCT and METAL LEVEL, selects the price list line. The price list line stores the base price, and a link to the appropriate formula.

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 9

Since the OPTION uplifts differ between GOLD, SILVER and BRONZE, we created a formula for each case, and applied the appropriate formula to the line. Below you can see that the formula SUPREME GOLD uses the values of the other 2 attributes, DURATION and OPTION to finish the pricing calculation.

The result is, when you order a SERVER A, and select GOLD for your metal level, 24 hour onsite service option, and a duration of 2 years, the following occurs: 1. 2. The price list line with $2220 is selected because of PRODUCT and METAL LEVEL The formula calculates to 2220 x 1.2 x 1.9 = $5061.60

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 10

CONCLUSION

The enhancements to the Attribute Mapping methodology in 11.5.8 have streamlined the pricing business process. Using a structured methodology like the one described here opens up tremendous flexibility in pricing techniques, and can have a massive favorable impact on maintenance costs and ROI.

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 11

APPENDIX A: Java Functional Companion WriteConfigAttributes


import import import import import import import import oracle.apps.fnd.common.Context; oracle.apps.cz.cio.*; com.sun.java.util.collections.*; java.sql.*; oracle.apps.cz.utilities.*; java.io.*; java.text.*; java.util.StringTokenizer;

public class WriteConfigAttributes extends AutoFunctionalCompanion { Configuration config = null; String mserviceItemName = "SUPREME"; //"SM SERVICE SUPREME"; ArrayList mselServiceNodes = new ArrayList(); String mQpPropName = "QPValue"; String mffContext = "SMQPValue"; String mMetalLevelFeat = "Service Medal Level"; String mMetalLevel = ""; String mMetalLevelAttr = "ATTRIBUTE1"; String mDurationFeat = "Duration"; String mDuration = ""; String mDurationAttr = "ATTRIBUTE2"; String mOptionsFeat = "Service Options"; String mOptions = ""; String mOptionsAttr = "ATTRIBUTE3"; String mServerFeat = "Server Platforms"; String mStorageFeat = "StorEdge Device"; String mProdAttr = "ATTRIBUTE4"; public WriteConfigAttributes() { } public void afterSave() { config = getRuntimeNode().getConfiguration(); // First, clear all attribute data for this configuration from // the CZ_CONFIG_ATTRIBUTES table. try { clearConfigAttributes(config); } catch (SQLException sqle) { throw new RuntimeException("Error in clearing the previous attribute data"); } // Starting from the root, get all the service bom items selected. RuntimeNode root = (RuntimeNode)config.getRootComponent(); if (root instanceof BomNode) { traverseBomTree(root); } else { Iterator iter = root.getChildren().iterator(); while (iter.hasNext()) { RuntimeNode child = (RuntimeNode)iter.next(); if (child instanceof BomNode || child instanceof ComponentSet) { traverseBomTree(child); } }

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 12

} int noNodes = mselServiceNodes.size(); // get the sevice level attributes mMetalLevel = getQpValue((IRuntimeNode)root, mMetalLevelFeat); mDuration = getQpValue((IRuntimeNode)root, mDurationFeat); mOptions = getQpValue((IRuntimeNode)root, mOptionsFeat); // write the config data writeAttributes(); } private String getQpValue (IRuntimeNode currRoot, String featName) { String ret = ""; IRuntimeNode mlIrtn = getFeaturebyName(currRoot, featName); if (mlIrtn != null) { try { IOption optSel = ((IOptionFeature)mlIrtn).getSelectedOption(); if (optSel == null) { ret = "none"; } else { Property qpProp = ((RuntimeNode)optSel).getPropertyByName(mQpPropName); if (qpProp != null && qpProp.hasStringValue()) { ret = qpProp.getStringValue(); } } } catch (SelectionNotMutexedException snme) { ret = "none"; } } return ret; } /** * Clears all attribute data for this configuration from * the CZ_CONFIG_ATTRIBUTES table. */ public void clearConfigAttributes(Configuration config) throws SQLException { long configHdrId = config.getConfigHeaderIdLong(); long configHdrRev = config.getConfigHeaderRevisionLong(); Connection conn = config.getContext().getJDBCConnection(); PreparedStatement pStmt = null; int ret; try { String sql = "DELETE FROM CZ_CONFIG_ATTRIBUTES WHERE CONFIG_HDR_ID=? AND CONFIG_REV_NBR=?"; pStmt = conn.prepareStatement(sql); pStmt.setLong(1, configHdrId); pStmt.setLong(2, configHdrRev); ret = pStmt.executeUpdate(); } finally { if (pStmt != null) pStmt.close(); } } private void traverseBomTree(RuntimeNode node) {

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 13

//List childAttribs = null; if (!(node instanceof ComponentSet)) { BomNode bomNode = (BomNode)node; String nodeName = bomNode.getName(); if (nodeName.compareToIgnoreCase(mserviceItemName) == 0 && bomNode.isSelected()) { mselServiceNodes.add(bomNode); } } // Now recursively visit all the children subtrees. Iterator iter = node.getChildren().iterator(); while (iter.hasNext()) { RuntimeNode child = (RuntimeNode)iter.next(); if (child instanceof BomNode || child instanceof ComponentSet) { traverseBomTree(child); } } } //____________________________________________________________________________ private IRuntimeNode getFeaturebyName (IRuntimeNode curRoot, String featName){ boolean isComp = false; IRuntimeNode retFeat = null; // get to a component to start if (curRoot.getType() == (IRuntimeNode.COMPONENT) || curRoot.getType() == (IRuntimeNode.COMPONENT_SET) || curRoot.getType() == IRuntimeNode.BOM_MODEL || curRoot.getType() == IRuntimeNode.BOM_OPTION_CLASS) { isComp = true; } while (isComp != true) { curRoot = curRoot.getParent(); int type = curRoot.getType(); if (curRoot.getType() == (IRuntimeNode.COMPONENT) || curRoot.getType() == (IRuntimeNode.COMPONENT_SET)|| curRoot.getType() == IRuntimeNode.BOM_MODEL || curRoot.getType() == IRuntimeNode.BOM_OPTION_CLASS) { isComp = true; } } retFeat = getFeatureInComponent(curRoot, featName); // if feature not found go to parent and test while (retFeat == null && !curRoot.isRoot()) { curRoot = curRoot.getParent(); retFeat = getFeatureInComponent(curRoot, featName); } return (retFeat); } //____________________________________________________________________________ private IRuntimeNode getFeatureInComponent (IRuntimeNode curRoot, String featName){ boolean ffound = false; IRuntimeNode retFeat = null; List compList = curRoot.getChildren(); Iterator cit = compList.iterator();

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 14

while (cit.hasNext() && !ffound) { IRuntimeNode irtn = (IRuntimeNode)cit.next(); if (irtn.getType() == (IRuntimeNode.OPTION_FEATURE) || irtn.getType() == (IRuntimeNode.BOOLEAN_FEATURE) || irtn.getType() == (IRuntimeNode.COUNT_FEATURE) || irtn.getType() == (IRuntimeNode.COUNT_FEATURE) || irtn.getType() == (IRuntimeNode.DECIMAL_FEATURE) || irtn.getType() == (IRuntimeNode.INTEGER_FEATURE) || irtn.getType() == (IRuntimeNode.TEXT_FEATURE)) { String fname = irtn.getName(); if (featName.compareTo(fname) == 0) { ffound = true; retFeat = irtn; } } if (irtn.getType() == (IRuntimeNode.COMPONENT) || irtn.getType() == (IRuntimeNode.COMPONENT_SET)) { retFeat = getFeatureInComponent(irtn, featName); if (retFeat != null) { ffound = true; } } } return (retFeat); } private void writeAttributes() { String context = mffContext; IRuntimeNode prodIrtn = null; HashMap attrValuePair = new HashMap(); attrValuePair.put(mMetalLevelAttr, mMetalLevel); attrValuePair.put(mDurationAttr, mDuration); attrValuePair.put(mOptionsAttr, mOptions); Iterator iter = mselServiceNodes.iterator(); while (iter.hasNext()) { BomNode node = (BomNode)iter.next(); // get the product name IRuntimeNode svrbnIrtn = getFeaturebyName((IRuntimeNode)node,mServerFeat); IRuntimeNode strbnIrtn = getFeaturebyName((IRuntimeNode)node,mStorageFeat); if (svrbnIrtn != null) { String fname = svrbnIrtn.getName(); prodIrtn = svrbnIrtn; } else if (strbnIrtn != null) { String fname = strbnIrtn.getName(); prodIrtn = strbnIrtn; } if (prodIrtn != null) { try { IOption optSel = ((IOptionFeature)prodIrtn).getSelectedOption(); String prodName = ((IRuntimeNode)optSel).getName(); attrValuePair.put(mProdAttr, prodName); } catch (SelectionNotMutexedException snme) { String junk = "here";; } }

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 15

insertAttributes(node, context, attrValuePair, config.getContext()); } } /** * Inserts the attributes belonging to a given node and context into * the CZ_CONFIG_ATTRIBUTES table. */ public int insertAttributes (BomNode node, String attrContext, Map attributes, Context ctx) { Connection conn = ctx.getJDBCConnection(ctx); PreparedStatement pStmt = null; int ret; try { try { String insertString = " INSERT INTO CZ_CONFIG_ATTRIBUTES (CONFIG_HDR_ID, CONFIG_REV_NBR, " + "CONFIG_ITEM_ID, ATTRIBUTE_CATEGORY"; String valueString = " VALUES(?,?,?,?"; // Build an insert string based on the attribute name for the attribute context. Collection keys = attributes.keySet(); Iterator keyIter = keys.iterator(); while (keyIter.hasNext()) { //insertString = insertString + "," + getAttributeFieldName (attrContext,(String)keyIter.next(), ctx); insertString = insertString + "," + (String)keyIter.next(); valueString = valueString + ",?"; } insertString = insertString + ")"; valueString = valueString + ")"; String sql = insertString + valueString; pStmt = conn.prepareStatement(sql); //Bind/set values to the parameters // Add config_hdr_id. pStmt.setLong(1,config.getConfigHeaderIdLong()); // Add config_rev_nbr. pStmt.setLong(2,config.getConfigHeaderRevisionLong()); // Add config_item_id. pStmt.setLong(3,node.getConfigItemID()); // Add flexfield context for attribute. pStmt.setString(4,attrContext); // Iterate over attributes. int n = 5; List attrValues = new ArrayList(); attrValues.addAll(attributes.values()); Iterator iter = attrValues.iterator(); while (iter.hasNext()) { pStmt.setString(n++, iter.next().toString()); } ret = pStmt.executeUpdate(); } finally { if(pStmt != null) pStmt.close(); } } catch (SQLException e) { throw new RuntimeException("Error Inserting into CZ_CONFIG_ATTRIBUTES table" + e);

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 16

} return ret; } /** * Return the column name in CZ_CONFIG_ATTRIBUTES to write this attribute value to, * given an attribute context name, attribute name, and database context. * This only applies to flexfield attributes */ public String getAttributeFieldName (String attrContext, String attribute, Context ctx) { Connection conn = ctx.getJDBCConnection(ctx); ResultSet rs = null; PreparedStatement pStmt = null; String sql, columnName = null; try { try { sql = "SELECT dfu.application_column_name " + "FROM FND_DESCR_FLEX_COLUMN_USAGES DFU, " + "FND_DESCRIPTIVE_FLEXS FDL , FND_APPLICATION FAN " + "WHERE fan.application_short_name = 'CZ' " + "AND dfu.application_id = fan.application_id " + "AND fdl.application_id = fan.application_id " + "AND fdl.APPLICATION_TABLE_NAME = 'CZ_CONFIG_ATTRIBUTES' " + "AND dfu.descriptive_flexfield_name = fdl.descriptive_flexfield_name " + "AND dfu.descriptive_flex_context_code = ? " + "AND end_user_column_name = ? "; pStmt = conn.prepareStatement(sql); pStmt.setString(1,attrContext ); pStmt.setString(2,attribute ); rs = pStmt.executeQuery(); if (rs.next()) { columnName = rs.getString(1); } else { throw new RuntimeException("No column name found for context = " + attrContext + " and attribute = " + attribute); } } finally { // close all if (rs != null) rs.close(); if (pStmt != null) pStmt.close(); } } catch (SQLException e) { throw new RuntimeException("Error querying attribute names"); } return columnName; } }

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 17

APPENDIX B: PL/SQL function Get_SM_Price_Attr in package sm_pricing


CREATE OR REPLACE PACKAGE sm_pricing AS FUNCTION Get_SM_Price_Attr(p_calling_app IN VARCHAR2, p_type IN VARCHAR2, p_context IN VARCHAR2, p_config_hdr_id IN NUMBER, p_config_rev_nbr IN NUMBER, p_id IN NUMBER) RETURN VARCHAR2; END sm_pricing;

CREATE OR REPLACE PACKAGE BODY sm_pricing AS FUNCTION Get_SM_Price_Attr(p_calling_app IN VARCHAR2, p_type IN VARCHAR2, p_context IN VARCHAR2, p_config_hdr_id IN NUMBER, p_config_rev_nbr IN NUMBER, p_id IN NUMBER) RETURN VARCHAR2 IS v_config_item_id v_return BEGIN IF p_calling_app = 'ASO' THEN SELECT INTO FROM WHERE AND AND config_item_id v_config_item_id aso_quote_line_details quote_line_id = p_id config_header_id = p_config_hdr_id config_revision_num = p_config_rev_nbr; NUMBER := null; VARCHAR2(10);

ELSIF p_calling_app = 'ONT' THEN v_config_item_id := p_id; ELSE v_return := null; END IF; IF v_config_item_id is not null THEN IF p_type = 'PROD' THEN SELECT INTO FROM ATTRIBUTE4 v_return cz_config_attributes

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 18

WHERE AND AND AND

config_hdr_id = p_config_hdr_id config_rev_nbr = p_config_rev_nbr config_item_id = v_config_item_id attribute_category = p_context;

ELSIF p_type ='METAL-LEVEL' THEN SELECT INTO FROM WHERE AND AND AND ATTRIBUTE1 v_return cz_config_attributes config_hdr_id = p_config_hdr_id config_rev_nbr = p_config_rev_nbr config_item_id = v_config_item_id attribute_category = p_context;

ELSIF p_type ='DURATION' THEN SELECT INTO FROM WHERE AND AND AND ATTRIBUTE2 v_return cz_config_attributes config_hdr_id = p_config_hdr_id config_rev_nbr = p_config_rev_nbr config_item_id = v_config_item_id attribute_category = p_context;

ELSIF p_type ='OPTION' THEN SELECT INTO FROM WHERE AND AND AND ELSE v_return := null; END IF; ELSE v_return := null; END IF; RETURN v_return; END Get_SM_Price_Attr; END sm_pricing; / .] ATTRIBUTE3 v_return cz_config_attributes config_hdr_id = p_config_hdr_id config_rev_nbr = p_config_rev_nbr config_item_id = v_config_item_id attribute_category = p_context;

QP: Leveraging Oracle Configurator and Attribute Mapping PAGE 19

ORACLE 11i Advanced Pricing QP: Leveraging Oracle Configurator and Attribute Mapping March 2003 Author: Rick Welling Contributing Authors: Michael Dellorso Oracle Corporation World Headquarters 500 Oracle Parkway Redwood Shores, CA 94065 U.S.A. Worldwide Inquiries: Phone: +1.650.506.7000 Fax: +1.650.506.7200 www.oracle.com Oracle Corporation provides the software that powers the internet. Oracle is a registered trademark of Oracle Corporation. Various product and service names referenced herein may be trademarks of Oracle Corporation. All other product and service names mentioned may be trademarks of their respective owners. Copyright 2003 Oracle Corporation All rights reserved.