Skip to content

F&O Hack – Calculated attribute values in F&O product configurator

Remarks: This is an old post that I haven’t gotten around to publish. I haven’t checked if this is still the best approach…

I recently spoke with one of my colleagues about using details from the source document to calculate without having the end-user fill them out.

The product configurator (PC) is handled entirely outside F&O in MSF, and XML files are used to exchange information. So, to get the calculated values over to MSF, it was required to change these XML files.
When the document configurator adds default values, it first looks at defaults from the product configuration model. If they are not defined, it then looks at the attribute types. This is our entry point.
XML Information about PC components is handled in PCXmlSessionWriterComponent, and the defaulted values are initialized in createAttributeDefaultValuesMap.

The issue is that this class needs to learn about the source document!
Here is where Price Models in PC come into the frame – We can “abuse” it to transfer values between the PC classes…

Price Models instances (PCRuntinePriceModelInstance) are initialized directly from the source document, which is a good entry into getting the references.
Start by adding a RecId and TableId variable to PCRuntimePriceModelInstance

[ExtensionOf(classStr(PCRuntimePriceModelInstance))]
public final class PCRuntimePriceModelInstance_Class_JFM_Extension
{
    public TableId sourceTableId;
    public RecId   sourceRecId;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        sourceTableId,
        sourceRecId
    #endmacro

    public container pack()
    {

....

To initialize the variables, make a CoC method for PCRuntimePriceModelInstanceFactory.create

[ExtensionOf(classStr(PCRuntimePriceModelInstanceFactory))]
public final class PCRuntimePriceModelInstanceFactory_Class_JFM_Extension
{
    public PCRuntimePriceModelInstance create(
        CurrencyCode                _currencyCode,
        RefRecId                    _priceModel,
        Common                      _sourceDocumentLine,
        PCproductConfigurationModel _productConfigurationModel,
        PCRuntimeMode               _runtimeMode,
        boolean                     _isRunningMultiCompany)
    {
        PCRuntimePriceModelInstance priceModelInstance = next create(
            _currencyCode,
            _priceModel,
            _sourceDocumentLine,
            _productConfigurationModel,
            _runtimeMode,
            _isRunningMultiCompany);

        priceModelInstance.sourceRecId = _sourceDocumentLine.RecId;
        priceModelInstance.sourceTableId = _sourceDocumentLine.TableId;

        return priceModelInstance;
    }

}

Finaly, create an extension class for PCXmlSessionWriteComponent, and add your own logic.


In my case, I have added a new enumerator to attribute types

[ExtensionOf(classStr(PCXmlSessionWriterComponent))]
public final class PCXmlSessionWriterComponent_Class_JFM_Extension
{
    private TableId sourceTableId;
    private RecId   sourceRecId;

    public void write(  PCClass                             _component,
                        str                                 _assignedComponentID,
                        PCXmlWriter                         _attributeTypeWriter,
                        PCXmlWriter                         _componentTreeWriter,
                        PCComponentInstance                 _componentInstance,
                        PCConfigurationControl              _modelUIControl,
                        PCComponentControl                  _componentControl,
                        Set                                 _parsedTypes,
                        PCXmlSessionDatabaseRelationTypes   _parsedSystemTableConstraintTypes,
                        Map                                 _parsedIntegerDomains,
                        LanguageId                          _sessionLanguage,
                        StackBase                           _subComponentPath,
                        PCRuntimeInstanceIdManager          _runtimeInstanceIdManager,
                        PCPriceMethod                       _priceMethod,
                        PCRuntimePriceModelInstance         _runtimePriceModelInstance)
    {
        sourceTableId = _runtimePriceModelInstance.sourceTableId;
        sourceRecId = _runtimePriceModelInstance.sourceRecId;

        next write(
            _component,
            _assignedComponentID,
            _attributeTypeWriter,
            _componentTreeWriter,
            _componentInstance,
            _modelUIControl,
            _componentControl,
            _parsedTypes,
            _parsedSystemTableConstraintTypes,
            _parsedIntegerDomains,
            _sessionLanguage,
            _subComponentPath,
            _runtimeInstanceIdManager,
            _priceMethod,
            _runtimePriceModelInstance);
    }

    protected Map createAttributeDefaultValuesMap(PCClass _component, PCComponentInstance _componentInstance)
    {
        EcoResAttributeDefaultValue defaultValue;
        EcoResAttribute             ecoResAttribute;
        EcoResCategoryAttribute     categoryAttribute;
        EcoResAttributeType         ecoResAttributeType;
        InventLocationId            inventLocationId;
        SalesQty                    salesQty;
        ItemId                      itemId;
        ProjId                      projId;

        Map attributeDefaultValueMap = next createAttributeDefaultValuesMap(_component, _componentInstance);

        switch (sourceTableId)
        {
            case tableNum(SalesQuotationLine):
                var salesQuotationLine = SalesQuotationLine::findRecId(sourceRecIdPGS);
                inventLocationId = salesQuotationLine.inventDim().inventLocationId;
                salesQty = salesQuotationLine.SalesQty;
                itemId = salesQuotationLine.ItemId;
                projId = salesQuotationLine.QuotationId;
                break;
            case tableNum(SalesLine):
                var salesLine = SalesLine::findRecId(sourceRecIdPGS);
                inventLocationId = salesLine.inventDim().InventLocationId;
                salesQty = salesLine.SalesQty;
                itemId = salesLine.ItemId;
                projId = salesLine.ProjId;
                break;
            default:
                return attributeDefaultValueMap;
        }

        while select EcoResAttributePCDefaultingTypePGS from ecoResAttributeType
            where ecoResAttributeType.EcoResAttributePCDefaultingType != EcoResAttributePCDefaultingType::None
            join ecoResAttribute
                where ecoResAttribute.AttributeType == ecoResAttributeType.RecId
                join RecId from categoryAttribute
                    where   categoryAttribute.Attribute == ecoResAttribute.RecId
                    &&      categoryAttribute.Category  == _component.RecId
        {
            switch(ecoResAttributeType.EcoResAttributePCDefaultingType)
            {
                case EcoResAttributePCDefaultingType::Warehouse:
                    attributeDefaultValueMap.insert(categoryAttribute.RecId, inventLocationId);
                    break;
                case EcoResAttributePCDefaultingType::SalesQuantity:
                    attributeDefaultValueMap.insert(categoryAttribute.RecId, any2Str(salesQty));
                    break;
                case EcoResAttributePCDefaultingType::ItemId:
                    attributeDefaultValueMap.insert(categoryAttribute.RecId, itemId);
                    break;
                case EcoResAttributePCDefaultingType::ProjId:
                    attributeDefaultValueMap.insert(categoryAttribute.RecId, projId);
                    break;
            }
        }

        return attributeDefaultValueMap;
    }
}

The way I implemented the logic, was to extend the product attribute type with a new enumerated value:

Leave a Reply

Your email address will not be published. Required fields are marked *