How to Extend Sales Order Update Functionality to Custom Fields in D365 Finance and Operations

By Bill Thompson | February 10, 2019

My colleague Jeremy Dobler wrote this nice article on how to extend sales order update functionality to custom fields in AX 2012. In this post, I'm going to show how to implement sales order update functionality to custom fields in Dynamics 365 Finance and Operations, and what changes are necessary when comparing to Dynamics AX.

Sales Order Update Functionality D365 Finance and Operations vs. Dynamics AX

New Fields

Jeremy's article states the following:

"Once you have added the fields to the SalesTable and the SalesLine table and exposed the fields on the SalesTable form, add the new custom field to the HeaderToLineUpdate field group on the SalesTable Table."

In D365FO, the new fields will need to be added via a table extension.  Create the extensions on the SalesTable and SalesLine tables and add the desired fields. Once that is completed, add the fields to the HeaderToLineUpdate field group.

Extend Sales order in D365

Update Line Fields

Next Jeremy's article states the following:

"In order for the system to recognize the sales order to line fields that need to be updated, the fields must be set to prompt, or always, in the Account Receivable parameters form. (Accounts receivable – Setup – Accounts receivable parameters – Updates – Update order lines (Button))

To add this field to the update order lines form, you will need to modify the lineUpdateDescription method in the SalesTable2LineField class."

In D365FO we have to use the lineUpdateDescriptionDelegate object to subscribe and write our custom code:

Extend Sales Order in D365

Code:

class SalesTable2LineField_Extension
{  
    /// <summary>
    /// Additional logic for custom fields
    /// </summary>
    /// <param name="_fieldId"></param>
    /// <param name="_tableId"></param>
    /// <param name="_result"></param>
    [SubscribesToclassStr(SalesTable2LineField), delegateStr(SalesTable2LineField, lineUpdateDescriptionDelegate))]
    public static void SalesTable2LineField_lineUpdateDescriptionDelegate(FieldId _fieldId, TableId _tableId, EventHandlerResult _result)
    {
        str pName = '';
        switch (_fieldId)
        {
            case fieldNum(SalesTable,CustomerOperationCode) :
                pName = fieldId2PName(tableNum(SalesLine),fieldNum(SalesLine,CustomerOperationCode));
                break;

            case fieldNum(SalesTable,CustomerSiteCode) :
                pName = fieldId2PName(tableNum(SalesLine),fieldNum(SalesLine,CustomerSiteCode));
                break;

            case fieldNum(SalesTable,SplitGroupCode) :
                pName = fieldId2PName(tableNum(SalesLine),fieldNum(SalesLine,SplitGroupCode));
                break;

            case fieldNum(SalesTable,LevGrowingSeasonId) :
                pName = fieldId2PName(tableNum(SalesLine),fieldNum(SalesLine,LevGrowingSeasonId));
                break;

            case fieldNum(SalesTable,SalesPeriodId) :
                pName = fieldId2PName(tableNum(SalesLine),fieldNum(SalesLine,SalesPeriodId));
                break;
        }

        _result.result(pName);
        
    }
    

}

Once this is complete if you go back to the AR parameters form, you will now find your custom field listed on the Update order lines form. Note: make sure to mark the field as prompt.

Extend Sales Order in D365

Sales Order Lines

Jeremy's article then states:

"The sales order line update process uses the sales order document service, so you will need to add some custom code for the system to copy the value from the sales header to the sales lines."

In D365FO, we can’t overlayer, so we are going to need to create an extension on the AxSalesTable class.  Here is the code:

[ExtensionOf(classStr(AxSalesTable))]
final class AxSalesTable_Extension
{

Custom Code:

    public LevCustomerOperationCode parmCustomerOperationCode(LevCustomerOperationCode _customerOperationCode = '')
    {
        if (!prmIsDefault(_customerOperationCode))
        {
            this.setField(fieldNum(SalesTable,CustomerOperationCode),_customerOperationCode);
        }

        return salesTable.CustomerOperationCode;
    }

    
    public LevCustomerSiteCode parmCustomerSiteCode(LevCustomerSiteCode _customerSiteCode = '')
    {
        if (!prmIsDefault(_customerSiteCode))
        {
            this.setField(fieldNum(SalesTable,CustomerSiteCode),_customerSiteCode);
        }

        return salesTable.CustomerSiteCode;
    }

    
    public LevSplitGroupCode parmSplitGroupCode(LevSplitGroupCode _splitGroupCode = '')
    {
        if (!prmIsDefault(_splitGroupCode))
        {
            this.setField(fieldNum(SalesTable,splitGroupCode),_splitGroupCode);
        }

        return salesTable.splitGroupCode;
    }

    
    public LevGrowingSeasonId parmLevGrowingSeasonId(LevGrowingSeasonId _LevGrowingSeasonId = '')
    {
        if (!prmIsDefault(_LevGrowingSeasonId))
        {
            this.setField(fieldNum(SalesTable,LevGrowingSeasonId),_LevGrowingSeasonId);
        }

        return salesTable.LevGrowingSeasonId;
    }

    
    public LevSalesPeriodId parmSalesPeriodId(LevSalesPeriodId _SalesPeriodId = '')
    {
        if (!prmIsDefault(_SalesPeriodId))
        {
            this.setField(fieldNum(SalesTable,SalesPeriodId),_SalesPeriodId);
        }

        return salesTable.SalesPeriodId;
    }

Create extension of AxSalesLine class:

[ExtensionOf(classStr(AxSalesLine))]
final class AxSalesLine_Extension
{

Custom Code:

    public LevCustomerOperationCode parmCustomerOperationCode(LevCustomerOperationCode _customerOperationCode = '')
    {
        if (!prmIsDefault(_customerOperationCode))
        {
            this.setField(fieldNum(SalesLine,CustomerOperationCode),_customerOperationCode);
        }

        return salesLine.CustomerOperationCode;
    }

    
    protected void set_CustomerOperationCode()
    {
        if (this.isMethodExecuted(funcName(), fieldNum(SalesLine,CustomerOperationCode)))
        {
            return;
        }

        this.setAxSalesTableFields();

        if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,CustomerOperationCode)))
        {
            this.parmcustomerOperationCode(this.axSalesTable().parmCustomerOperationCode());
        }
    }

    
    public LevCustomerSiteCode parmCustomerSiteCode(LevCustomerSiteCode _customerSiteCode = '')
    {
        if (!prmIsDefault(_customerSiteCode))
        {
            this.setField(fieldNum(SalesLine,CustomerSiteCode),_customerSiteCode);
        }

        return salesLine.CustomerSiteCode;
    }

    
    protected void set_CustomerSiteCode()
    {
        if (this.isMethodExecuted(funcName(), fieldNum(SalesLine,CustomerSiteCode)))
        {
            return;
        }

        this.setAxSalesTableFields();

        if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,CustomerSiteCode)))
        {
            this.parmCustomerSiteCode(this.axSalesTable().parmCustomerSiteCode());
        }
    }

    
    public LevSplitGroupCode parmSplitGroupCode(LevSplitGroupCode _splitGroupCode = '')
    {
        if (!prmIsDefault(_splitGroupCode))
        {
            this.setField(fieldNum(SalesLine,splitGroupCode),_splitGroupCode);
        }

        return salesLine.splitGroupCode;
    }

    
    protected void set_SplitGroupCode()
    {
        if (this.isMethodExecuted(funcName(), fieldNum(SalesLine,SplitGroupCode)))
        {
            return;
        }

        this.setAxSalesTableFields();

        if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,SplitGroupCode)))
        {
            this.parmSplitGroupCode(this.axSalesTable().parmSplitGroupCode());
        }
    }

    
    public LevGrowingSeasonId parmLevGrowingSeasonId(LevGrowingSeasonId _LevGrowingSeasonId = '')
    {
        if (!prmIsDefault(_LevGrowingSeasonId))
        {
            this.setField(fieldNum(SalesLine,LevGrowingSeasonId),_LevGrowingSeasonId);
        }

        return salesLine.LevGrowingSeasonId;
    }

    
    protected void set_LevGrowingSeasonId()
    {
        if (this.isMethodExecuted(funcName(), fieldNum(SalesLine,LevGrowingSeasonId)))
        {
            return;
        }

        this.setAxSalesTableFields();

        if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,LevGrowingSeasonId)))
        {
            this.parmLevGrowingSeasonId(this.axSalesTable().parmLevGrowingSeasonId());
        }
    }

    
    public LevSalesPeriodId parmSalesPeriodId(LevSalesPeriodId _SalesPeriodId = '')
    {
        if (!prmIsDefault(_SalesPeriodId))
        {
            this.setField(fieldNum(SalesLine,SalesPeriodId),_SalesPeriodId);
        }

        return salesLine.SalesPeriodId;
    }

    protected void set_SalesPeriodId()
    {
        if (this.isMethodExecuted(funcName(), fieldNum(SalesLine,SalesPeriodId)))
        {
            return;
        }

        this.setAxSalesTableFields();

        if (this.isAxSalesTableFieldsSet() || this.AxSalesTable().isFieldModified(fieldNum(SalesTable,SalesPeriodId)))
        {
            this.parmSalesPeriodId(this.axSalesTable().parmSalesPeriodId());
        }
    }

Extension to Sales Line

Jeremy then has this in his article:

"The final piece to the puzzle is to add the set*YourCustomField* method to the setTableFields method in the AxSalesLine class"

Again, as we can’t overlayer, we will work in an extension of the AxSalesLine class.  We already have an extension class for AxSalesLine, so create this one last method to replace the AX 2012 step:

protected void setTableFields()
    {
        next setTableFields();

        useMapPolicy = false;

        this.set_CustomerOperationCode();
        this.set_CustomerSiteCode();
        this.set_SplitGroupCode();
        this.set_LevGrowingSeasonId();
        this.set_SalesPeriodId();

        useMapPolicy = true;
    }

This uses Chain of Command in D365, and we want our code to run post the base class, so we use nextSetTableFields() prior to running our custom code.

We hope this tip helps you change over from your Dynamics AX, or learn new in D365FO. If you have any more questions about how to extend sales order update functionality to custom fields in D365FO, please feel free to contact us.

Related Posts


Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.

Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.

Start the Conversation

It’s our mission to help clients win. We’d love to talk to you about the right business solutions to help you achieve your goals.

Subscribe To Our Blog

Sign up to get periodic updates on the latest posts.

Thank you for subscribing!