Monday, March 18, 2013

Microsoft Dynamics AX 2012 AIF – Bills of Materials Web Service

Microsoft Dynamics AX 2012 AIF – Bills of Materials Web Service
 
Purpose: The purpose of this document is to illustrate how to use standard Bills of Materials Web Service in Microsoft Dynamics AX 2012 RTM/FPK/R2.
 
Challenge: Data model changes in Microsoft Dynamics AX 2012 related to high normalization and introduction of surrogate keys made some imports more complex. The data model implementing Bill of materials business entity was not dramatically changed and natural keys (BOMId, InventDimId) are still used to form a link between involved tables. In fact there're certain nuances related to number sequences, inventory dimensions, approval and activation processes, etc. when you consume standard Bills of Materials Web Service in Microsoft Dynamics AX 2012.
 
Solution: Microsoft Dynamics AX 2012 ships with standard Bills of Materials Web Service which I'll use for import of Bills of Materials.
 
Assumption: Products, released products and other required reference data is already created.
 
Data Model:
 
Table Name
Table Description
BOMTable
The BOMTable table contains all of the bills of materials. A bill of materials is associated with a site and an item group. For each bill of materials, the information is stored about whether it has been approved and by whom.
BOMVersion
The BOMVersion table contains all the BOM versions. This table connects to the BOMTable table in order to specify the BOM to which the version refers, and it connects to the InventTable table to specify to which item the BOM version is assigned. The BOMVersion table also connects to the InventDim table to specify a site for the BOM version. Additionally, the BOMVersion table stores information about the approval and activation for each BOM version.
BOM
The BOM table contains the bill of materials lines. A BOM line connects to an item to consume and a BOM version to which the line applies.
InventDim
The InventDim table contains values for inventory dimensions.
 
Data Model Diagram:
 
<![if !vml]><![endif]>
 
Walkthrough:
 
BomBillsOfMaterialsService Web Service
 
 
AxdBillsOfMaterials Query
 
Please note that AxdBillsOfMaterials Query is used by BomBillsOfMaterialsService Web Service
 
BOM Number sequence
 
Please note that I checked 2 checkboxes in Allow user changes section (To a lower number [V] and To a higher number [V]). Doing so will allow to explicitly assign BOMId from integration client when consuming Bills of Materials Web Service and at the same time for business users using Microsoft Dynamics AX 2012 UI the system will still automatically assign BOMId from number sequence   
 
Released product
 
Please note that for respective released product in order to successfully import Bills of Materials information Production type should be "BOM". Otherwise you will see the following warning
 
Microsoft Dynamics AX 2012 Warning
 
 
Service group: AlexManufacturingServices
 

Please note that I created a new Service group "AlexManufacturingServices" and included standard Bills of Materials Web Service into this Service group
 
Inbound port: AlexManufacturingServices
 

Please note that Basic Inbound port will be automatically created and activated after you deploy Service group
 
Service reference: AlexManufacturingServices
 
In integration client (.NET Console application) I'll add Service reference using WSDL URI
 
Integration client – Source code
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ConsoleApplication1.ServiceReference1;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                const string BOMId = "BOM_ALEXBOM1";
 
                BillsofMaterialsServiceClient client = new BillsofMaterialsServiceClient();
                CallContext context = new CallContext();
 
                AxdBillsOfMaterials bom = new AxdBillsOfMaterials();
 
                bom.BOMVersion = new AxdEntity_BOMVersion[1];
                bom.BOMVersion[0] = new AxdEntity_BOMVersion();
 
                bom.BOMVersion[0].BOMId = BOMId;
                bom.BOMVersion[0].Name = "ALEXBOM";
                bom.BOMVersion[0].ItemId = "ALEXPRODUCT";
                bom.BOMVersion[0].InventDimId = "Ax#1";
                //bom.BOMVersion[0].InventDim = new AxdEntity_InventDim[1];
                //bom.BOMVersion[0].InventDim[0] = new AxdEntity_InventDim();
                //bom.BOMVersion[0].InventDim[0].InventSiteId = "1";
               
 
                bom.BOMVersion[0].BOMTable = new AxdEntity_BOMTable[1];
                bom.BOMVersion[0].BOMTable[0] = new AxdEntity_BOMTable();
                bom.BOMVersion[0].BOMTable[0].BOMId = BOMId;
                bom.BOMVersion[0].BOMTable[0].Name = "ALEXBOM";
                bom.BOMVersion[0].BOMTable[0].SiteId = "1";
 
                bom.BOMVersion[0].BOMTable[0].BOM = new AxdEntity_BOM[1];
                bom.BOMVersion[0].BOMTable[0].BOM[0] = new AxdEntity_BOM();
                bom.BOMVersion[0].BOMTable[0].BOM[0].BOMId = BOMId;
                bom.BOMVersion[0].BOMTable[0].BOM[0].ItemId = "ALEXITEM";
                bom.BOMVersion[0].BOMTable[0].BOM[0].BOMQty = 1;
                bom.BOMVersion[0].BOMTable[0].BOM[0].BOMQtySpecified = true;
                bom.BOMVersion[0].BOMTable[0].BOM[0].UnitId = "ea";
                bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimId = "Ax#1";
                //bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM = new AxdEntity_InventDimBOM[1];
                // bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0] = new AxdEntity_InventDimBOM();
                // bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0].InventSiteId = "1";
 
 
                EntityKey[] entityKey = client.create(context, bom);
 
                Console.WriteLine("Done!");
                Console.ReadLine();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error!" + ex.Message);
                Console.ReadLine();
            }
        }
    }
}
 
XML Request
 
<?xml version="1.0" encoding="UTF-8"?>
<BillsofMaterialsServiceCreateRequest xmlns="http://schemas.microsoft.com/dynamics/2008/01/services">
  <BillsOfMaterials xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/BillsOfMaterials">
    <SenderId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></SenderId>
    <BOMVersion class="entity">
      <_DocumentHash xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></_DocumentHash>
      <Approver xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></Approver>
      <BOMId>BOM_ALEXBOM1</BOMId>
      <InventDimId>Ax#1</InventDimId>
      <ItemId>ALEXPRODUCT</ItemId>
      <Name>ALEXBOM</Name>
      <PmfBulkParent xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></PmfBulkParent>
      <BOMTable class="entity">
        <Approver xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></Approver>
        <BOMId>BOM_ALEXBOM1</BOMId>
        <ItemGroupId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></ItemGroupId>
        <Name>ALEXBOM</Name>
        <SiteId>1</SiteId>
        <BOM class="entity">
          <BOMId>BOM_ALEXBOM1</BOMId>
          <BOMQty>1</BOMQty>
          <ConfigGroupId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></ConfigGroupId>
          <InventDimId>Ax#1</InventDimId>
          <ItemBOMId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></ItemBOMId>
          <ItemId>ALEXITEM</ItemId>
          <ItemPBAId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></ItemPBAId>
          <ItemRouteId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></ItemRouteId>
          <PmfPlanGroupId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></PmfPlanGroupId>
          <Position xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></Position>
          <UnitId>ea</UnitId>
          <VendId xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></VendId>
        </BOM>
      </BOMTable>
    </BOMVersion>
  </BillsOfMaterials>
</BillsofMaterialsServiceCreateRequest>
 
XML Response
 
<?xml version="1.0" encoding="UTF-8"?>
<BillsofMaterialsServiceCreateResponse xmlns="http://schemas.microsoft.com/dynamics/2008/01/services">
  <EntityKeyList xmlns="http://schemas.microsoft.com/dynamics/2006/02/documents/EntityKeyList">
    <EntityKey xmlns="http://schemas.microsoft.com/dynamics/2006/02/documents/EntityKey">
      <KeyData>
        <KeyField>
          <Field>RecId</Field>
          <Value>5747147639</Value>
        </KeyField>
      </KeyData>
    </EntityKey>
  </EntityKeyList>
</BillsofMaterialsServiceCreateResponse>
 
AxdBillsOfMaterials.prepareForSave Method – Source code
 
public boolean prepareForSave(AxdStack _axdStack,  str _dataSourceName)
{
    AxBOMTable              axBOMTable;
    AxBOM                   axBOM;
    AxInternalBase          parentAxBC;
    container               findInventDimResult;
    InventDim               localInventDim;
 
    AxInventDim             axInventDim;
    AxInventDim_BOM         axInventDim_BOM;
    AxInventDim_BOMVersion  axInventDim_BOMVersion;
    ;
 
    switch (classidget(_axdStack.top()))
    {
        case classnum(AxBOMVersion) :
            axBOMVersion = _axdStack.top();
            axBOMVersion.parmActive(NoYes::No);
            axBOMVersion.parmApproved(NoYes::No);
 
            isBOMVersionSaved = false;
            return false;
 
        case classnum(AxBOMTable) :
            axBOMTable      = _axdStack.top();
            axBOMVersion    = axBOMTable.parentAxBC();
            axBOMTable.parmBOMId(axBOMVersion.parmBOMId());
            axBOMTable.parmApproved(NoYes::No);
 
            // Set InventDimId on the BOMVersion to the same one as on the BOMTable in order to avoid failure in BOMVersion.validateWrite().
            // Later on the correct InventDimId will be assigned and validated during AxInventDim_BOMVersion.save().
            if(axBOMTable.parmSiteId())
            {
                localInventDim.clear();
                localInventDim.InventSiteId = axBOMTable.parmSiteId();
                localInventDim = InventDim::findOrCreate(localInventDim);
                axBOMVersion.parmInventDimId(localInventDim.InventDimId);
            }
 
            return true;
 
        case classnum(AxBOM) :
            axBOM           = _axdStack.top();
            axBOMTable    = axBOM.parentAxBC();
            axBOM.parmBOMId(axBOMTable.parmBOMId());
 
            findInventDimResult = AxInventDim::findInventDimInventTable(axBOM.axInventTable().inventTable(),
                                                                        false,
                                                                        axBOMTable.bomTable().SiteId);
            if (conpeek(findInventDimResult,1))
            {
                localInventDim = conpeek(findInventDimResult,2);
 
                axBOM.parmInventDimId(localInventDim.InventDimId);
                axBOM.axInventDim().allDimensions(localInventDim);
                axBOM.setInventDimIdDirtySaved(conpeek(findInventDimResult,3));
            }
            else
            {
                axBOM.parmInventDimId(InventDim::inventDimIdBlank());
            }
 
            return true;
 
        case classnum(AxInventDim) :
            axInventDim = _axdStack.top();
            parentAxBC  = axInventDim.parentAxBC();
 
            switch(classidget(parentAxBC))
            {
                case classnum(AxBOM)        :
 
                    axBOM   = parentAxBC;
 
                    this.checkInventDim(axInventDim,
                                        axBOM.parmItemId(),
                                        axBOM.axInventTable().inventTable());
                    axBOM.axInventDim().resetInternalValues();
                    axInventDim.moveAxInventDim(axBOM.axInventDim());
 
                    _axdStack.pop();
                    axInventDim_BOM = AxInventDim_BOM::newAxBOM(axBOM);
                    _axdStack.push(axInventDim_BOM);
 
                    break;
 
                case classnum(AxBOMVersion) :
 
                    axBOMVersion    = parentAxBC;
 
                    this.checkInventDim(axInventDim,
                                        axBOMVersion.parmItemId(),
                                        axBOMVersion.axInventTable().inventTable());
                    axBOMVersion.axInventDim().resetInternalValues();
                    axInventDim.moveAxInventDim(axBOMVersion.axInventDim());
 
                    _axdStack.pop();
                    axInventDim_BOMVersion = AxInventDim_BOMVersion::newAxBOMVersion(axBOMVersion);
                    _axdStack.push(axInventDim_BOMVersion);
 
                    break;
 
                default:
                    error(strfmt("@SYS94402", classId2Name(classidget(parentAxBC))));
            }
 
            return true;
 
        default :
            error(strfmt("@SYS88979",classId2Name(classidget(_axdStack.top()))));
            return false;
    }
    return false;
}
 
Remark: The prepareForSave method is called immediately before saving the top node of the stack
 
Please note that prepareForSave method usually contains validation business logic or additional business logic which must be executed before the record is saved
 
Result
 
Microsoft Dynamics AX 2012 – Bill of materials (Versions)
 

 
Microsoft Dynamics AX 2012 – Bill of materials (Visual designer)
 

 
Microsoft Dynamics AX 2012 – Bill of materials (Lines)
 

 
Microsoft Dynamics AX 2012 – Bill of materials (Approve/Activate)
 
 
 
Please note that Bill of Materials Approval and Activation processes are implemented in Microsoft Dynamics AX 2012 UI as dedicated functions which also allow to specify who is performing an action (Approver). That's why you are not supposed to approve or activate BOM Version, or approve BOM when consuming Bills of Materials Web Service. Please see more details in AxdBillsOfMaterials.prepareForSave method   
 
        case classnum(AxBOMVersion) :
            axBOMVersion = _axdStack.top();
            axBOMVersion.parmActive(NoYes::No);
            axBOMVersion.parmApproved(NoYes::No);
 
            isBOMVersionSaved = false;
            return false;
 
        case classnum(AxBOMTable) :
            axBOMTable      = _axdStack.top();
            axBOMVersion    = axBOMTable.parentAxBC();
            axBOMTable.parmBOMId(axBOMVersion.parmBOMId());
            axBOMTable.parmApproved(NoYes::No);
 
However it's certainly possible to implement automatic Approval and Activation without modifying AxdBillsOfMaterials.prepareForSave method. This can be done by overriding AxdBillsOfMaterials.updateNow method where you can use Bill of Materials Approval and Activation API
 
public void updateNow()
{
    BOMApprove              bomApprove;
 
    BOMRouteVersionApprove  versionApprove;
    BOMRouteVersionActivate versionActivate;
 
    super();
       
    bomApprove = BOMApprove::newBOMTable(BOMTable));   
    bomApprove.parmRemove(NoYes::No);
    bomApprove.parmApprover(HcmWorker::findByPersonnelNumber("000131").RecId);
    bomApprove.run();
   
    versionApprove = BOMRouteVersionApprove::newBOMVersion(BOMVersion);   
    versionApprove.parmRemove(NoYes::No);
    versionApprove.parmApprover(HcmWorker::findByPersonnelNumber("000131").RecId);
    versionApprove.run();
   
    versionActivate = BOMRouteVersionActivate::newBOMVersion(BOMVersion);  
    versionActivate.run();
}
 
Please note that Approver field in BOMVersion and BOMTable tables is RecID reference to HCMWorker table. This is how you can easily convert current AX user into appropriate RecID reference in HCMWorker table
 
HcmWorker::userId2Worker(curuserid());
 
Summary: Please consider using standard Bills of Materials Web Service for import of Bills of Materials into Microsoft Dynamics AX 2012. In this walkthrough I demonstrated how to consume standard Bills of Materials Web Service for import of Bills of Materials.
 
Author: Alex Anikiiev, PhD, MCP
 
Tags: Microsoft Dynamics ERP, Microsoft Dynamics AX 2012, Data Import, Data Conversion, Data Migration, Application Integration Framework, Bills of Materials.
 
Note: This document is intended for information purposes only, presented as it is with no warranties from the author. This document may be updated with more content to better outline the concepts and describe the examples.

4 comments:

  1. Hi Alex, nice post!! I´m trying to insert 2 items on my BOM, but a have some troubles inserting them.
    How can I insert more than one item on BOM Table, I have problemin the index array... I changed 0´s to 1´s

    Best Regards

    ReplyDelete
    Replies

    1. //FIRST ITEM
      bom.BOMVersion[0].BOMTable[0].BOM = new AxdEntity_BOM[2];
      bom.BOMVersion[0].BOMTable[0].BOM[0] = new AxdEntity_BOM();
      bom.BOMVersion[0].BOMTable[0].BOM[0].BOMId = BOMId;
      bom.BOMVersion[0].BOMTable[0].BOM[0].ItemId = "00000-01";
      bom.BOMVersion[0].BOMTable[0].BOM[0].BOMQty = 1;
      bom.BOMVersion[0].BOMTable[0].BOM[0].BOMQtySpecified = true;
      bom.BOMVersion[0].BOMTable[0].BOM[0].UnitId = "ea";
      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimId = "1-DIM-545515";//"1-DIM-000246";
      bom.BOMVersion[0].BOMTable[0].BOM[0].LineNum = 1;

      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM = new AxdEntity_InventDimBOM[1];
      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0] = new AxdEntity_InventDimBOM();
      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0].InventSiteId = "PB";
      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0].InventSizeId = "NA";
      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0].InventColorId = "BKA";
      bom.BOMVersion[0].BOMTable[0].BOM[0].InventDimBOM[0].InventLocationId = "FG1";

      //////SECOND ITEM
      bom.BOMVersion[0].BOMTable[0].BOM[1] = new AxdEntity_BOM();
      bom.BOMVersion[0].BOMTable[0].BOM[1].BOMId = BOMId;
      bom.BOMVersion[0].BOMTable[0].BOM[1].ItemId = "00000-02";
      bom.BOMVersion[0].BOMTable[0].BOM[1].BOMQty = 1;
      bom.BOMVersion[0].BOMTable[0].BOM[1].BOMQtySpecified = true;
      bom.BOMVersion[0].BOMTable[0].BOM[1].UnitId = "ea";
      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimId = "1-DIM-545515";//"1-DIM-000246";
      bom.BOMVersion[0].BOMTable[0].BOM[1].LineNum = 1;

      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimBOM = new AxdEntity_InventDimBOM[1];
      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimBOM[0] = new AxdEntity_InventDimBOM();
      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimBOM[0].InventSiteId = "PB";
      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimBOM[0].InventSizeId = "NA";
      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimBOM[0].InventColorId = "BKA";
      bom.BOMVersion[0].BOMTable[0].BOM[1].InventDimBOM[0].InventLocationId = "FG1";

      Delete
  2. Hello, Any way to let the system assign automatically the BOM ID getting it from the sequence number??

    ReplyDelete
  3. I want to share a testimony on how Le_Meridian funding service helped me with loan of 2,000,000.00 USD to finance my marijuana farm project , I'm very grateful and i promised to share this legit funding company to anyone looking for way to expand his or her business project.the company is UK/USA funding company. Anyone seeking for finance support should contact them on lfdsloans@outlook.com Or lfdsloans@lemeridianfds.com Mr Benjamin is also on whatsapp 1-989-394-3740 to make things easy for any applicant. 

    ReplyDelete