
Data type, table and form extensions
- Create a new string EDT and call it ERPPCStringAttribute
- Create table extensions for the BOM and ProdBOM and add a new field based on the ERPPCStringAttribute EDT
- Create a form extension for the ProdBOM and add the new field in the grid (Tab > Overview > Grid)
Extend the PCBOMLineDetails form
This is a tricky part. In the form for the BOM line details in the product configuration model, create a section for the attribute that looks the same like all the others. Here is a screenshot from Visual Studio:

Extend PCBOMLineDetails Form UI logic
You cannot overwrite methods on UI elements in form extensions e.g. clicked() . Therefore you have to implement the logic for the UI elements in a separate class. Create a new class ERPPCBomLineDetailsEventHandler and implement the following UI logic for clicked(), modified() and lookup()class ERPPCBomLineDetailsEventHandler { [FormControlEventHandler(formControlStr(PCBOMLineDetails, AllocateStringAttribute), FormControlEventType::Clicked)] public static void AllocateAttributeString1_OnClicked(FormControl sender, FormControlEventArgs e) { FormCheckBoxControl chx = sender; FormControl allocationGroup_AttributeString1 = sender.formRun().design().controlName(formControlStr( PCBOMLineDetails, allocationGroup_StringAttribute) ); FormControl attributeString1_Allocation = sender.formRun().design().controlName(formControlStr( PCBOMLineDetails, StringAttribute_Allocation) ); PCModelingLibrary::templateSetEnabledStatus( (chx.value() == NoYes::Yes), allocationGroup_AttributeString1, attributeString1_Allocation ); } [FormControlEventHandler(formControlStr(PCBOMLineDetails, StringAttribute_Allocation), FormControlEventType::Modified)] public static void AttributeString1_Allocation_OnModified( FormControl sender, FormControlEventArgs e) { FormRadioControl radio = sender; FormStringControl attributeString1 = sender.formRun().design().controlName(formControlStr( PCBOMLineDetails, StringAttribute) ); str label = attributeString1.labelText(); attributeString1.text(”); if (radio.selection() == PCAllocation::Value) { attributeString1.extendedDataType( extendedTypeNum(ERPPCStringAttribute) ); } else { attributeString1.extendedDataType( extendedTypeNum(ERPPCStringAttribute) ); attributeString1.label(label); } } [FormControlEventHandler(formControlStr(PCBOMLineDetails, StringAttribute), FormControlEventType::Lookup)] public static void AttributeString1_OnLookup(FormControl sender, FormControlEventArgs e) { Object PCBOMLineDetails = sender.formRun(); PCClass component = PCBOMLineDetails.component(); FormRadioControl radio = sender.formRun().design().controlName(formControlStr( PCBOMLineDetails, StringAttribute_Allocation) ); if(radio.selection() == PCAllocation::Attribute) { PCModelingLibrary::attributeLookup(sender, component); } } }
Extend the PCBOMLineDetails class-behind
Like many forms in Dynamics 365 for Finance and Operations the form has a class-behind that implements the business logic. You need to extend this class in order to deal with the newly created Attribute group. Create a new class ERPPCBomLineDetails_Extension and impelement the following logic:[ExtensionOf(formStr(PCBOMLineDetails))] final class ERPPCBomLineDetails_Extension { public PCClass component() { return component; } [ExtensionOf(FormMethodStr(PCBOMLineDetails,loadAllocations))] public void loadAllocations() { PCTemplateAttributeBinding templateAttributeBinding; PCTemplateAttribute fieldReference; PCTemplateAttributeBinding findBindingByIDs(TableId _tableId, FieldId _fieldId) { PCTemplateAttributeBinding binding; fieldReference = templateFind.findTemplateAttributeByTableIdAndFieldId( _tableId, _fieldId ); select firstonly binding where binding.TemplateAttribute == fieldReference.RecId && binding.TemplateComponent == templateComponent.RecId; return binding; } next loadAllocations(); templateAttributeBinding = findBindingByIDs(tableNum(BOM), fieldNum(BOM, ERPPCStringAttribute)); PCModelingLibrary::templateLoadStringAllocation(component, templateAttributeBinding, StringAttribute, AllocateStringAttribute, StringAttribute_Allocation, AllocationGroup_StringAttribute); } [ExtensionOf(FormMethodStr(PCBOMLineDetails,updateRadioControls))] public void updateRadioControls() { next updateRadioControls(); PCModelingLibrary::templateSetEnabledStatus( (AllocateStringAttribute.value() == NoYes::Yes), AllocationGroup_StringAttribute, StringAttribute_Allocation); } [ExtensionOf(FormMethodStr(PCBOMLineDetails,writeAllocations))] public void writeAllocations() { PCTemplateAttribute fieldReference; next writeAllocations(); if ((AllocateStringAttribute.value() == NoYes::Yes)) { fieldReference = templateFind.findTemplateAttributeByTableIdAndFieldId( tableNum(BOM), fieldNum(BOM, ERPPCStringAttribute)); PCModelingLibrary::templateSaveStringAllocation(component, templateComponent, fieldReference, StringAttribute, StringAttribute_Allocation); } } }
Extend the product configuration model framework
Implement the following classes to extend the product configuration model framework in Dynamics 365 Finance and Operations: PcAdaptorBOMLine class:[ExtensionOf(classStr(PcAdaptorBOMLine))] final class ERPPcAdaptorBOMLine_Extension { public ERPPCStringAttribute parmWANPCStringAttribute( ERPPCStringAttribute _stringAttribute = bom.ERPPCStringAttribute) { EcoResTextValue value; value.TextValue = _stringAttribute; this.fieldAssignment(bom.TableId, fieldnum(BOM, ERPPCStringAttribute), value); bom.ERPPCStringAttribute = _stringAttribute; return bom.ERPPCStringAttribute; } }PCGenerateBOMLine
[ExtensionOf(classStr(PCGenerateBOMLine))] final class ERPPCGenerateBOMLine_Extension { protected void setupBOM( BOMId _bomId, InventDim _inventDim, InventTable _inventTable, boolean _isProduction ) { next setupBom(_bomId,_inventDim,_inventTable,_isProduction); bom.ERPPCStringAttribute = templateFind.getBindingValueAsString( tableNum(BOM), fieldNum(BOM,ERPPCStringAttribute)); } }
Re-Initialize the framework template records
By default the framework does not recognize to create template records for the newly added attribute field. Therefore you have to delete the existing templates and trigger the framework to reinitialize. Be aware, this might harm your existing models! PCTemplateInitialize class:[ExtensionOf(classStr(PcTemplateInitialize))] final class ERPPCTemplateInitialize_Extension { public static void main(Args _args) { PCTemplate tableTemplate; PcTemplateInitialize init = PcTemplateInitialize::construct(); delete_from tableTemplate; init.run(); } protected void createTemplatesForBOM() { next createTemplatesForBom(); PCTemplate tableTemplate; select firstonly tableTemplate where tableTemplate.ReferencedTableId == tableNum(BOM); this.createFieldTemplate(tableTemplate,fieldNum(BOM, ERPPCStringAttribute )); } }Start this class from Class Runner or add a menu item to call it by hand.
Test your Implementation
- Create a new product configuration model
- Add a new attribute “notes” of type string
- Add a BOM line to the model
- Open the BOM line details and assign the notes attribute to the string attribute 1

- Save the model
- Create and approve the model version
- Create a new production order
- Use the configuration wizard to provide a text value for the notes attribute

- Create the production order
- Verify that you can see the attribute value in the ProdBOM of your production order
