The Infolog

A blog of Dynamics AX development tips and tricks

Skip to: Content | Sidebar | Footer

Creating a configured sales line

15 September, 2015 (09:59) | Dynamics AX | By: Howard Webb

Recently a customer has asked us to develop a way to produce a sales line which contained an item that fully configured via product configurator. For those that are unaware of the process if an item is selected that is configurable you can answer a number of “questions” to allow configuration of an item via the below button on the sales line:

This allows you to customise the item in a number of ways, for example if you are making a sofa which can have a number of coloured threads you can choose the thread colour.

The issue with this is that the configuration form uses .Net to produce the values via some .dll files which we cannot examine. In the end with a lot of debugging we found out that it used XML to get the configuration questions and build the form, then returned XML.

To replicate this we build a class that read the XML, then pulled the answers from a table in a Columbus solution (although this could be any place that stores information).

Firstly we need to make use of a number of framework classes (which will use the default configuration to get the questions) after the sales line has been created:

</p><p><span style="font-family:Consolas; font-size:8pt">    pcConfigurationProductVariantFactory    = pcConfigurationProductVariantFactory::construct();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    vairentContainer                        = pcConfigurationProductVariantFactory.createVariant(ecoResProductMaster, SalesLine.ItemId, SalesLine.InventDimId);  <span style="color:green"><em>//This creates a new inventDIm so will need to update sales line post everything being done</em></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">    newInventDimId                          = <span style="color:darkblue"><strong>conPeek</strong></span>(vairentContainer, <span style="color:red"><strong>2</strong></span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcVariantConfiguration                  = pcVariantConfiguration::find(<span style="color:darkblue"><strong>conPeek</strong></span>(vairentContainer, <span style="color:red"><strong>1</strong></span>));  <span style="color:green"><em>//gets the new varient record</em></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">    productModelVersion                     = PCProductModelVersion::findActiveVersion(ecoResProductMaster.RecId);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    productConfigurationModel               = productModelVersion.getProductConfigurationModel();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    xmlDocument                             = XmlDocument::newXml(productConfigurationModel.getXML());
</span></p><p>

The XML document will contain the list of attributes, operation lines and subparts. This will need looping through to get the individual questions. In my example I have used maps to record these items.

With the list of questions generated we can then start to generate the return XML with the questions and answers as per below. In the below example we get the answers from a Columbus solution’s table:

 </p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:darkblue"><strong>private</strong></span>
      <span style="color:darkblue"><strong>void</strong></span> createAttributeElements()
</span></p><p><span style="font-family:Consolas; font-size:8pt">{
</span></p><p><span style="font-family:Consolas; font-size:8pt">    OSSProfileFieldValue        ossProfileFieldValue;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    OSSProfileField             ossProfileField;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    String255                   questionName, questionDisplayName, questionType;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    XmlNode                     attributeNode;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    XmlAttribute                attributeNameAttribute, attributeIdAttribute, attributeSelectedAttribute, attributeTypeAttribute, attributeValueAttribute;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>int</strong></span>                         numOfDec;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>utcDateTime</strong></span>                 dateTime;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    TimeHour24                  emptyTime;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    InventSizeConversionTable   inventSizeConversionTable;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    ;
</span></p><p>
 </p><p> </p><p><span style="font-family:Consolas; font-size:8pt">    mapEnumerator               = attributes.getEnumerator();
</span></p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>while</strong></span>(mapEnumerator.moveNext())
</span></p><p><span style="font-family:Consolas; font-size:8pt">    {
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">        questionDetails             = mapEnumerator.currentValue();
</span></p><p><span style="font-family:Consolas; font-size:8pt">        questionName                = <span style="color:darkblue"><strong>conPeek</strong></span>(questionDetails, <span style="color:red"><strong>1</strong></span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">        questionDisplayName         = <span style="color:darkblue"><strong>conPeek</strong></span>(questionDetails, <span style="color:red"><strong>2</strong></span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">        questionType                = <span style="color:darkblue"><strong>conPeek</strong></span>(questionDetails, <span style="color:red"><strong>3</strong></span>);
</span></p><p>
 </p><p>
</p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>select</strong></span>
      <span style="color:darkblue"><strong>firstOnly</strong></span> RecId <span style="color:darkblue"><strong>from</strong></span> ossProfileField
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>where</strong></span>   ossProfileField.Withdrawn       == NoYes::No
</span></p><p><span style="font-family:Consolas; font-size:8pt">            &amp;&amp;      ossProfileField.EntityTableId   == <span style="color:darkblue"><strong>tableNum</strong></span>(SalesLine)
</span></p><p><span style="font-family:Consolas; font-size:8pt">            &amp;&amp;      ossProfileField.FieldName       == questionName
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>join</strong></span> ossProfileFieldValue
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>where</strong></span>   ossProfileFieldValue.EntityRecId        == salesLine.RecId
</span></p><p><span style="font-family:Consolas; font-size:8pt">                &amp;&amp;      ossProfileFieldValue.EntityTableId      == ossProfileField.EntityTableId
</span></p><p><span style="font-family:Consolas; font-size:8pt">                &amp;&amp;      ossProfileFieldValue.FieldName          == ossProfileField.FieldName;
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>if</strong></span>(ossProfileFieldValue)
</span></p><p><span style="font-family:Consolas; font-size:8pt">        {
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode               = configuredXML.createElement   (#attribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNameAttribute      = configuredXML.createAttribute (#nameAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeIdAttribute        = configuredXML.createAttribute (#uniqueIdAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeSelectedAttribute  = configuredXML.createAttribute (#isUserSelectedAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeTypeAttribute      = configuredXML.createAttribute (#typeAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeValueAttribute     = configuredXML.createAttribute (#valueAttribute);
</span></p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">            attributeIdAttribute.       value(MapEnumerator.currentKey());
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeSelectedAttribute. value(<span style="color:darkred">'1'</span>);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNameAttribute.     value(questionName);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeTypeAttribute.     value(questionType);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:green"><em>//This area needs further testing</em></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>switch</strong></span>(questionType)
</span></p><p><span style="font-family:Consolas; font-size:8pt">            {
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Text) :
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>if</strong></span>(questionName == #sizeProperty)
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    {
</span></p><p><span style="font-family:Consolas; font-size:8pt">                        inventSizeConversionTable = inventSizeConversionTable::find(salesLine.itemID, ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>if</strong></span>(inventSizeConversionTable)
</span></p><p><span style="font-family:Consolas; font-size:8pt">                            attributeValueAttribute.value(inventSizeConversionTable.SizeConversionAXValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>else</strong></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">                            attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    }
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>else</strong></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">                        attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> #enumType :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Currency) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(ossProfileFieldValue.StringValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Decimal) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    numOfDec = numOfDec(ossProfileFieldValue.RealValue);
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>num2str</strong></span>(  ossProfileFieldValue.RealValue,
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:red"><strong>0</strong></span>,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                            numOfDec,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                            DecimalSeparator::Auto,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                            ThousandSeparator::Auto));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::DateTime) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    dateTime    = DateTimeUtil::newDateTime(ossProfileFieldValue.DateValue, emptyTime);
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>datetime2str</strong></span>(dateTime, DateFlags::FormatAll));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::Integer) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>int2str</strong></span>(ossProfileFieldValue.IntValue));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>case</strong></span> enum2Value(AttributeDataType::TrueFalse) :
</span></p><p><span style="font-family:Consolas; font-size:8pt">                    attributeValueAttribute.value(<span style="color:darkblue"><strong>strfmt</strong></span>(<span style="color:darkred">"%1"</span>, ossProfileFieldValue.BoolValue));
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>default</strong></span> :
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>break</strong></span>;
</span></p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">            }
</span></p><p>
 </p><p>
 </p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeNameAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeIdAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeSelectedAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeTypeAttribute);
</span></p><p><span style="font-family:Consolas; font-size:8pt">            attributeNode.attributes().setNamedItem(attributeValueAttribute);
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">            componentNode.appendChild(attributeNode);
</span></p><p><span style="font-family:Consolas; font-size:8pt">        }
</span></p><p><span style="font-family:Consolas; font-size:8pt">    }
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">}
</span></p><p>

This will produce an XML document with the questions and the answers needed. With that XML we can then configure and update the sales line:

</p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:darkblue"><strong>private</strong></span>
      <span style="color:darkblue"><strong>void</strong></span> configureLine()
</span></p><p><span style="font-family:Consolas; font-size:8pt">{
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptorSourceDocumentLine             adaptorSourceDocumentLine;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptorFactory                        pcAdaptorFactory;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptor                               pcAdaptor;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCAdaptorProductConfigurationModel      adaptorProductConfigurationModel;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCXmlParseConfigurationInstance         pcXmlParseConfigurationInstance;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCBackEndConfiguration                  pcBackEndConfiguration;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    EcoResDistinctProductVariant            distinctProductVariant;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    PCSourceDocumentLineUtility             sourceDocumentLineUtility;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    InventDim                               inventDim;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    ;
</span></p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:white"><strong>    pcAdaptorFactory                    = PCAdaptorFactory::construct();</strong></span>
    </span></p><p><span style="font-family:Consolas; font-size:8pt">    adaptorProductConfigurationModel    = PCAdaptorProductConfigurationModel::construct(productConfigurationModel);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    adaptorSourceDocumentLine           = PCAdaptorSourceDocumentLine::construct(salesLine);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor                           = pcAdaptorFactory.getAdaptorFromModelName(productConfigurationModel.Name);
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor.parmProductConfigurationModel(adaptorProductConfigurationModel);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor.parmSourceDocumentLine(adaptorSourceDocumentLine);
</span></p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcXmlParseConfigurationInstance     = pcXmlParseConfigurationInstance::construct();
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcXmlParseConfigurationInstance.parse(productConfigurationModel, pcVariantConfiguration, <span style="color:darkred">''</span>, configuredXML.xml(), PCRuntimeMode::EditVariant, PCAdaptor);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcAdaptor.run();
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">    inventDim                           = InventDim::find(newInventDimId);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    pcBackEndConfiguration              = pcBackEndConfiguration::construct();
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">    pcBackEndConfiguration.setup(   pcVariantConfiguration,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    productModelVersion,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    salesLine.ItemId,
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>conPeek</strong></span>(vairentContainer, <span style="color:red"><strong>3</strong></span>),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    InventDim,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    InventDim,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    salesLine.SalesPrice,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    salesLine.ShippingDateRequested,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    pcAdaptor,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                    PCRuntimeLibrary::docuGenerateNoYes());
</span></p><p><span style="font-family:Consolas; font-size:8pt"><span style="color:white"><strong>    pcBackEndConfiguration.run();</strong></span>
    </span></p><p>
 </p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">    InventDim.ConfigId                  = pcBackEndConfiguration.getAppliedConfigurationName();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    inventDim                           = InventDim::findOrCreate(inventDim);
</span></p><p><span style="font-family:Consolas; font-size:8pt">    distinctProductVariant              = pcBackEndConfiguration.getAppliedDistinctProductVariant();
</span></p><p><span style="font-family:Consolas; font-size:8pt">    sourceDocumentLineUtility           = PCSourceDocumentLineUtility::construct();
</span></p><p>
 </p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>ttsBegin</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">    salesLine                   = sourceDocumentLineUtility.updateLineDetails(  salesLine,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                pcBackEndConfiguration.getGeneratedBOMId(),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                pcBackEndConfiguration.getGeneratedRouteId(),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                pcAdaptor.getPrice(),
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                salesLine.ShippingDateRequested,
</span></p><p><span style="font-family:Consolas; font-size:8pt">                                                                                InventDim.inventDimId);
</span></p><p><span style="font-family:Consolas; font-size:8pt">
      <span style="color:darkblue"><strong>ttsCommit</strong></span>;
</span></p><p><span style="font-family:Consolas; font-size:8pt">}
</span></p>

Print Friendly

Write a comment