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"> && ossProfileField.EntityTableId == <span style="color:darkblue"><strong>tableNum</strong></span>(SalesLine)
</span></p><p><span style="font-family:Consolas; font-size:8pt"> && 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"> && ossProfileFieldValue.EntityTableId == ossProfileField.EntityTableId
</span></p><p><span style="font-family:Consolas; font-size:8pt"> && 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>