AX 2012: Integrate Dynamics AX with Website

the-web-1

Challenge:

What if you need to integrate a Retail E-Commerce website with the powerful Dynamics AX ERP at backend to leverage numerous functional capabilities it offers in the areas like but not limited to Supply Chain Management. Then you are reading the right blog post! We can achieve it through exposing custom AIF services as web services on IIS using enhanced inbound ports via HTTP adapter. In this post, we’ll expose customers data in Dynamics AX 2012 persisted in CustTable to an external interface using web services.

To simplify things, we’ll create a read service operation and then test our custom AIF service exposed as a web service using .Net console application. Although this blog post uses C# consumer to consume the custom service but the same service can be consumed by any website development platforms like ASP.NET and the possibilities are limitless!

Prerequisites:

1. Full compile revealing no errors.
2. Full CIL revealing no errors.
3. Install web services on IIS.
4. Configure default AIF website created while performing step 3.

Development:

1. Create contract class MAKCustTableContract.

  • Declare class variables for chosen fields of CustTable. Use same EDTs as used by table fields.
  • Create parm methods for each of the class variables.
  • Some of the parm methods are given below for reference.
  • To generate parm methods automatically, read this post.
[DataContractAttribute]
class MAKCustTableContract
{
    CustAccount accountNum;
    CustBankAccountId bankAccount;
    CustCreditMaxMST creditMax;
    CustCreditRating creditRating;
    CustCurrencyCode currency;
    CustGroupId custGroup;
    CustDlvModeId dlvMode;
    DlvReasonId dlvReason;
    CustInvoiceAccount invoiceAccount;
    VendAccount vendAccount;
}
[DataMemberAttribute('AccountNum')]
public CustAccount parmAccountNum(CustAccount _accountNum = accountNum)
{
    accountNum = _accountNum;

    return accountNum;
}

[DataMemberAttribute('BankAccount')]
public CustBankAccountId parmBankAccount(CustBankAccountId _bankAccount = bankAccount)
{
    bankAccount = _bankAccount;

    return bankAccount;
}

3. Create service class MAKCustTableService.

  • Add readCustTable() method.
class MAKCustTableService
{
}
[
    SysEntryPointAttribute(true),
    AifCollectionTypeAttribute('return',Types::Class,classStr(MAKCustTableContract))
]
public List readCustTable()
{
    MAKCustTableContract    contract;
    List                    custlist = new List(Types::Class);
    CustTable               custTable;

    while select custTable
        where custTable.CustGroup == '20'
    {
        contract = new MAKCustTableContract();
        contract.parmAccountNum(custTable.AccountNum);
        contract.parmBankAccount(custTable.BankAccount);
        contract.parmCreditMax(custTable.CreditMax);
        contract.parmCreditRating(custTable.CreditRating);
        contract.parmCurrency(custTable.Currency);
        contract.parmCustGroup(custTable.CustGroup);
        contract.parmDlvMode(custTable.DlvMode);
        contract.parmDlvReason(custTable.DlvReason);
        contract.parmInvoiceAccount(custTable.InvoiceAccount);
        contract.parmVendAccount(custTable.VendAccount);
        
        custlist.addEnd(contract);
    }

    return custList;
}

4. Create new service MAKCustTableService.

  • Create a new service node under AOT > Services.

Untitled

  • Expand service node just created.
  • Right click on Operations to add new service operation and select the class method readCustTable.

Untitled

  • Right click MAKCustTableService > Add-Ins > Register service.
  • Make sure it shows NoError in the status.

Untitled

4. Create enhanced inbound port MAKCustTableServices.

  • Open Functional workspace.
  • Click System administration>Setup>Services and Application Integration Framework>Inbound ports.
  • Click New to create new inbound port.
  • Select HTTP for the adapter.
  • Select default AIF website in the URI.

Untitled

  • Select Service operations from the Service contract customizations fast tab.

Untitled

  • Activate the enhanced inbound port.

Untitled

5. Test the URI address of the enhanced inbound port in the browser to ensure the service is activated.

Untitled

5. Test the AIF custom service with C# consumer in .Net console application.

  • Open Visual Studio 2010.
  • Create new .Net console application project in C#.
  • Add new Service reference in the project Service References.
  • Give the URI of the enhanced inbound port we created in address.

Untitled

  • Give the Program definition as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MAKServiceConsumer
{
    using MAKCustTableServices;

    class Program
    {
        static void Main(string[] args)
        {
            CallContext context;
            MAKCustTableServiceClient client;
            List list;

            context = new CallContext();
            context.Company = "CEU";

            client = new MAKCustTableServiceClient();
            list = client.readCustTable(context).ToList();
        }
    }
}
  • Debug the program to see the web service returning data 🙂

Untitled

Advertisements

AX 2012: Create Custom Workflow

Purpose:

The purpose of this document is to describe how we can develop a custom workflow template.

Business requirement:

Ability to have approval process for customers. Standard AX does not offer any workflow for customers approval out-of-the-box.

Unit testing prerequisites:

The following steps must be completed before a workflow can be unit tested.

  1. Configure workflow execution account.
  2. Configure workflow batch jobs

Development:

1. Create a workflow status Enum to have the following elements:

customer-approval-workflow

2. Add field of type workflow status enum to the CustTable table.
3. Override canSubmitToWorkflow() method of CustTable table to define workflow submission criteria.

public boolean canSubmitToWorkflow(str _workflowType = '')
{
    boolean canSubmit = false;

    if (custTable.RecId &&
	custTable.MzkWorkflowApprovalStatus == MzkCustWFApprovalStatus::NotSubmitted)
    {
        canSubmit = true;
    }

    return canSubmit;
}

4. Add a static method on CustTable table to update workflow status. This method will be called from workflow event handlers and workflow approval event handlers.

static void mzkUpdateWorkflowStatus(RefRecId _recId, MzkCustWorkflowStatus _status)
{
    CustTable custTable;

    custTable = CustTable::findRecId(_recId, true);

    ttsBegin;
    custTable.MzkWorkflowApprovalStatus = _status;
    custTable.update();
    ttsCommit;
}

5. Create a Query for the CustTable table.

customer-approval-workflow

6. Create a Workflow Category.
7. Create a Workflow Type using wizard.

customer-approval-workflow

Where,

  • Category – Name of the workflow category.
  • Query – Name of the query.
  • Document menu item – Name of display menu item for the document form.

The following artifacts will be created:

  • Workflow type
  • Classes
    • Document class which extends WorkflowDocument.
    • EventHandler class which gives implementation to handle different workflow events.
    • SubmitManager class.
  • Action menu items:
    • SubmitMenuItem pointing to SubmitManager class.
    • CancelMenuItem pointing to WorkflowCancelManager class.

8. Enable Workflow on CustTable and CustTableListPage form by setting Design node properties as follows:

  • WorkflowEnabled – Yes.
  • WorkflowDatasource – Name of the form datasource, CustTable.
  • WorkflowType – Name of the custom workflow type created.

9. Give submit logic in SubmitManager class.

public static void main(Args _args)
{
    MzkCustWorkflowTypeSubmitManager submitManager;

    submitManager = new MzkCustWorkflowTypeSubmitManager();
    submitManager.submit(_args);
}
public void submit(Args _args)
{
    RecId                 _recId;
    WorkflowCorrelationId _workflowCorrelationId;
    workflowTypeName      _workflowTypeName;
    WorkflowComment       _initialNote;
    WorkflowSubmitDialog  workflowSubmitDialog;

    _recId = _args.record().RecId;
    _workflowTypeName = workFlowTypeStr("MzkCustWorkflowType");
    _initialNote = "";

    // Opens the submit to workflow dialog.
    workflowSubmitDialog = WorkflowSubmitDialog::construct(
	_args.caller().getActiveWorkflowConfiguration());

    workflowSubmitDialog.run();

    if (workflowSubmitDialog.parmIsClosedOK())
    {
        _recId = _args.record().RecId;
        // Get comments from the submit to workflow dialog.
        _initialNote = workflowSubmitDialog.parmWorkflowComment();

        try
        {
            ttsbegin;

            _workflowCorrelationId = Workflow::activateFromWorkflowType(
		_workflowTypeName, _recId, _initialNote, NoYes::No);

            ttscommit;

            // Updates the workflow button to diplay Actions instead of Submit.
            _args.caller().updateWorkflowControls();

            info("Submitted to workflow.");
        }
        catch(exception::Error)
        {
            error("Error on workflow activation.");
        }
    }
}

10. Create a Workflow Approval element using the wizard.

customer-approval-workflow

11. Drag the newly created approval to the Supported elements node of the custom workflow type.
12. Define workflow type event handlers in workflow type event handler class.

class MzkCustWorkflowTypeEventHandler implements
    WorkflowCanceledEventHandler,
    WorkflowCompletedEventHandler,
    WorkflowStartedEventHandler
{
}
public void started(WorkflowEventArgs _workflowEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::Submitted);
}
public void completed(WorkflowEventArgs _workflowEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::Completed);
}
public void canceled(WorkflowEventArgs _workflowEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::Canceled);
}

13. Define approval element event handlers in workflow approval element event handler class.

class MzkCustWFApprovalEventHandler implements
    WorkflowElementCanceledEventHandler,
    WorkflowElemChangeRequestedEventHandler,
    WorkflowElementCompletedEventHandler,
    WorkflowElementReturnedEventHandler,
    WorkflowElementStartedEventHandler,
    WorkflowElementDeniedEventHandler,
    WorkflowWorkItemsCreatedEventHandler
{
}
public void started(WorkflowElementEventArgs _workflowElementEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowElementEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::Submitted);
}
public void changeRequested(WorkflowElementEventArgs _workflowElementEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowElementEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::ChangeRequested);
}
public void canceled(WorkflowElementEventArgs _workflowElementEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowElementEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::PendingCancelation);
}
public void completed(WorkflowElementEventArgs _workflowElementEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowElementEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::Completed);
}
public void returned(WorkflowElementEventArgs _workflowElementEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
	_workflowElementEventArgs.parmWorkflowContext().parmRecId(),
	MzkCustWFApprovalStatus::Returned);
}
public void created(WorkflowWorkItemsEventArgs _workflowWorkItemsEventArgs)
{
    CustTable::mzkUpdateWorkflowStatus(
        _workflowWorkItemsEventArgs.parmWorkflowElementEventArgs().parmWorkflowContext().parmRecId(),
        MzkCustWFApprovalStatus::Created);
}

14. Define the resubmit action manager class.

public class MzkCustWFApprovalResubmitActionMgr
{
}
public static void main(Args _args)
{
    RecID                        recID;
    TableId                      tableId;
    CustTable                    custTable;
    WorkflowWorkItemTable        workItem;
    WorkflowWorkItemActionDialog workflowWorkItemActionDialog;
    
    recID = _args.record().RecId;
    tableId = _args.record().TableId;
    custTable = _args.record();
    workItem = _args.caller().getActiveWorkflowWorkItem();

    if (workItem.RecId > 0)
    {
        try
        {
            workflowWorkItemActionDialog = WorkflowWorkItemActionDialog::construct(
                workItem,
                WorkflowWorkItemActionType::Resubmit,
                new MenuFunction(_args.menuItemName(),_args.menuItemType()));

            workflowWorkItemActionDialog.run();

            if (workflowWorkItemActionDialog.parmIsClosedOK())
            {
                if (custTable.MzkWorkflowApprovalStatus ==
			MzkCustWFApprovalStatus::ChangeRequested)
                {
                    workItem = _args.caller().getActiveWorkflowWorkItem();
                    WorkflowWorkItemActionManager::dispatchWorkItemAction(workItem,
                        workflowWorkItemActionDialog.parmWorkflowComment(),
                        workflowWorkItemActionDialog.parmTargetUser(),
                        WorkflowWorkItemActionType::Resubmit,
                        _args.menuItemName(),
                        false);

                    custTable.MzkWorkflowApprovalStatus
			= MzkCustWFApprovalStatus::Submitted;

                    ttsbegin;
                    custTable.dataSource().write();
                    ttscommit;
                }
                else
                {
                    throw Exception::Error;
                }
            }
        }
        catch(Exception::Error)
        {
            throw error(strfmt("Cannot resubmit workflow."));
        }
    }

    _args.caller().updateWorkflowControls();
}

15. Design the Workflow.

  • Navigate to Accounts receivable > Setup > Accounts receivable workflows.
  • Create a new workflow instance of the workflow type you created.
  • Define the states from Start to End of the workflow.
    • Drag approval element from Toolbox on the left to the Designer pane on the right.
    • Connect the bottom of Start state with top of the Approval element.
    • Connect the bottom of Approval element to the top of End state.
  • Resolve any errors and warnings by setting workflow and approval element properties.
  • Activate it.

customer-approval-workflow

16. Test the workflow. You should be able to see the workflow bar.

customer-approval-workflow


AX 2012: Getting ReferenceGroup Control

Getting ReferenceGroup control automatically while dragging a datasource field to a form control is little tricky. Even if you have defined relations correctly at the table level, you might not always get the reference group control for a foreign key in the table. See the solution below for this issue.

Problem:

Consider for example, two tables:

1. FAZCourseTable
2. FAZEnrollmentTable

where FAZEnrollmentTable contains the foreign key of FAZCourseTable as follows:

Untitled

But when I drag this foreign key field from datasource to grid control, I get Int64Edit control instead of ReferenceGroup control.

Untitled

Resolution:

To get the ReferenceGroup control, perform the following development steps:

1. Create unique index on FAZCourseTable for the natural key of the table which in this case is Code field.
2. Set AllowDuplicates to No on the index properties.
3. Set AlternateKey to Yes on the index properties.

Untitled

4. Set ReplacementKey to CodeIdx, name of the index just created, on the table properties.

Untitled

5. Delete the Int64Edit control created earlier on the form.
6. Drag the FAZCourseTable field from the datasource to the grid control node.
7. You should be getting the ReferenceGroup control now.

Untitled