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

By Bill Thompson | February 10, 2019

How to extend sales order update functionality to custom fields in d365 finance and operations

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:

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:

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

Recommended Reading:

Manage U.S. Use Tax on Purchase Orders in Dynamics 365 Finance and Operations

  Managing sales tax requirements on your business purchase can be complicated, but Dynamics 365 Finance and Operations can help […]

Read the Article
5.19.22 Dynamics CRM

How to Write a Great Support Ticket in the Stoneridge Support Portal

Submitting a support ticket through the Stoneridge Support Portal is a quick and effective way to get assistance for any […]

Read the Article

Managing Your Business Through Uncertain Times Using Dynamics 365 Finance and Operations

  Dynamics 365 Finance and Operations (F&O) can help you make informed decisions on how to move your business forward. […]

Read the Article
5.13.22 Power Platform

Using Power BI Object Level Security

  The following article will demonstrate how to use Power BI Object Level Security to disable column data based on […]

Read the Article
5.12.22 Dynamics CRM

How to Use the Stoneridge Support Portal

Stoneridge Software’s support portal is an intuitive and useful function that makes it easy for you to access resources to […]

Read the Article
5.6.22 Dynamics GP

Dynamics GP Transaction Removal: Purchase Orders

  Are you having performance issues with Purchase Orders?  Do you find that there are old Purchase Orders on your […]

Read the Article
5.5.22 Dynamics GP

The Real Story about the Long-Term Future of Dynamics GP Support

I’ve seen a number of people put forward comment that Dynamics GP is going away and you have to get […]

Read the Article

New Features in Dynamics 365 Business Central 2022 Wave 1 Release – Financial Enhancements

The Dynamics 365 Businses Central 2022 Wave 1 Release has a lot of new and exciting features to help your […]

Read the Article
4.29.22 Dynamics GP

Dynamics GP Transaction Removals: Bank Reconciliation

  This is part 2 of a 3 part series on Dynamics GP Transaction Removals. These quick tips will hopefully […]

Read the Article

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!

X