Integrating a custom module with standard AX

Most modifications made to standard AX fall under the following categories:

  • Adding a field to a master table that is used for reporting
  • Adding a field to a table that is used by custom business logic
  • Changing the behavior of the standard business logic in order to align it with our organization's processes or extend it with new functionality

We will demonstrate this in the form of a series of modifications, further integrating our fleet management application with standard AX. To demonstrate this, we will associate our haulier table with the vendor table so that we can identify a haulier as a supplier. Vehicles tracked by our Fleet Management System might be supplied by external hauliers. In order to track this, we require the following:

  • Add a vendor ID reference to the vehicle table
  • Add the ability to mark the vendor as a vehicle servicing vendor
  • Add the ability to mark a purchase order as a vehicle service order

Adding the vendor ID reference to the vehicle table

This first part seems easy enough; we just need to drag the EDT for the supplier account into our vehicle table. To work out which EDT we should use, a good trick is to do the following:

  1. Navigate to Accounts payable | Common | Supplier | All suppliers and open the details form for any supplier.
  2. Right-click on the Supplier account field and choose Personalize.
  3. On the form that opens, there is a System name field. It tells us the control name, form data source name, table name and field name, as shown in the following screenshot:
    Adding the vendor ID reference to the vehicle table
  4. So, the field is VendTable.AccountNum. Open the AOT and then the properties for this field. The ExtendedDataType is VendAccount.
  5. Locate VendAccount in the AOT and drag it into our ConFMSVehicleTable vehicle table.
  6. You will receive this prompt: Do you want to add the ForeignKey relation from the EDT to the current table?. Click on Yes.
  7. We want the field to appear on the list page and details form. Since we have constructed field groups, we simply add the new field to the Overview and Details field groups.
  8. Go to Fleet management system | Common | Vehicles. Verify that the supplier account is there. If not, it means the grid doesn't reference the Overview field group, and it should be corrected.
  9. Open the vehicle details for any vehicle, verifying that the field is there. Also verify that the lookup works and that the field only allows valid supplier accounts.

Marking a vendor as a vehicle service provider

To mark a vendor as a vehicle service provider, the most obvious change would be to add a flag to the VendTable table. The problem here is that over time, with the addition of other similar requirements, we could acquire many checkboxes. This increases the chances of errors due to incorrect data setup; the easier we make the setup of maintenance of the master, the better.

We will instead try to use an existing functionality, and there are several possibilities that we are going to consider. The first two are explained here:

  • The first possible solution would be to declare that all members of a particular vendor group are considered vehicle service providers. We can do this by simply referencing the vendor group in a parameter, but this is limiting, especially since the vendor group is normally owned by the finance department and takes part in the integration with the general ledger.
  • The second possibility is to set up a table of vehicle service providers. We can then use the TableGroupAll pattern or provide the ability to store a query that allows the user to define the criteria of a vehicle service provider.

An example of the TableGroupAll pattern can be found by going to Accounts payable | Setup | Vendor posting profiles. In order to determine whether a supplier matches, code must be written; we can't simply use the criteria in a query.

You can see the query method by navigating to Warehouse management | Setup | Labor standard | Labor standards. Here, we have two buttons, Select items and Select locations, that allow the user to associate items and locations with a labor standard using a user-defined query. Both of these methods will be covered in the Exception handling section in Chapter 10, Advanced Development Techniques.

However, it turns out that no modification is needed to do this; Dynamics AX provides this ability in the form of the procurement catalogue.

Note

The procurement catalogue feature allows vendors to be "tagged" as being approved to provide goods and services from one or more categories. The catalogue is a hierarchy of item categories, whereby if the vendor is tagged with a high-level category, then it is also expected to be able to supply items from a more specialist category.

Our requirement almost exactly matches the purpose of the procurement catalogue. This demonstrates the importance of broad system knowledge—so that we can identify when a requirement does and does not require significant modifications. It is common that, as we start work, we notice that the system can already perform the task. AX is a highly functional system, and a consultant or key user can miss this. We can also see that changes are requested that could cause a performance issue or conflict with the way AX is designed, ricking regression on when updates are applied. We should push back to the consultant or key user and discuss the correct action. Everyone involved will want the footprint of our modifications to be as small as possible and, where a modification is necessary, be sympathetic to AX's design.

To use the procurement catalogue, we simply choose a procurement category to represent a vehicle service provider and then tag the vendors as needed. We will still require a system parameter to know which category identifies vehicle service providers, and we will also need to write a lookup so that we allow only suitable suppliers to the vehicles.

First, we will configure a vendor to be a vehicle supplier. In the USMF company of the Contoso demo dataset, the procurement category contains a VEHICLES category. We will assign this to US-102 and Tailspin Parts:

  1. Navigate to USMF | Accounts payable | Common | All vendors.
  2. Select US-102 and the General tab and click on Categories.
  3. In the form that opens, click on Add category and select VEHICLES from the Category drop-down list, as shown in the following screenshot:
    Marking a vendor as a vehicle service provider
  4. We can assign as many categories as we like to a supplier at various levels. Once this is done, close the form and jump back into the IDE.

Our requirement is to be able to mark the purchase orders as vehicle service orders, which we will do by checking whether the category to which the supplier is assigned is at or below the VEHICLES category. Therefore, we need to add a system parameter that will define which category identifies vehicle service providers. We will use this parameter to identify purchase orders as vehicle service orders, so we will add it to the Vendor parameters form. To do this, follow these steps:

We need to know which EDT to use in the same way as we did for the VendAcount EDT. Navigate to USMF | Accounts payable | Common | All vendors, select the General tab, and click on Categories.

Right-click on the Category field and select Personalization. This tells us that the form is DirPartyEcoResCategory and the field is VendCategory.Category.

The EDT we need is EcoResCategoryId. Drag this onto the field list of the VendParameters table. As usual, click on Yes when prompted to add a foreign key relation.

Note

We could use the personalization method to work out that the table name was VendParameters, but since it followed the naming conventions, it was easy to find. Using naming conventions makes development much easier for everyone.

  1. Rename the field to ConFMSServiceCategoryId. Descriptive field names are critical for getting maintainable code. Set Label to Vehicle service category.
  2. Save the changes, which results in the following error:
    'RelatedTableRole' conflicts with another 'RelatedTableRole' on relation EcoResCategory on table VendParameters.
  3. There is already a relation, and we need to do two things to correct this.
  4. First, the relation was created as EcoResCategory1. We should make this obvious with a proper name. Rename this to ConFMSServiceCategory. This won't fix the error but a correct name is very important.
  5. To fix the error, we need to make the relation unique by populating the RelatedTableRole property. This is meant to describe the role of the EcoResCategory relation. Enter ConFMSServiceCategory in this property.

    Note

    The internal RelatedTableRole is a combination of the table name and this property. It doesn't use the relation name.

  6. Complete the cardinality properties and save the table; the errors will be gone. You may need to compile the table again to convince the compiler that the relation is now unique.
  7. By looking at the design for the VendParameters form, you will notice a suitable field group to use—Vendor. Drag your new field into this field group.
  8. Drag the table into your project and save the project.
  9. Before we continue, we open the VendParameters form. The drop-down list for the new field isn't very useful; it's just a flat list of categories. We need a nice tree view lookup.

Reference group controls

The ConFMSServiceCategoryId field is a record ID, so in order to make this usable for the user, we have to show data that the field's relation references. Dynamics AX provides a special control for this, called ReferenceGroup.

By adding the field to the Vendor field group, AX will automatically create the control on the forms that reference this field group. Since the record ID field has a primary-key-based foreign key relation, AX will create a ReferenceGroup control.

In the AOT, navigate to Forms | VendParameters and then look at the properties of the Vendor_ConFMSServiceCategoryId control. This is in Designs | Design | Tab | GeneralBody | Vendor.

The control has a property, called ReferenceField, that has a value for the ConFMSServiceCategoryId reference field, and also one for ReplacementFieldGroup. This references a field group on the referenced table in order to replace the record ID field with one or more fields from the reference table.

It defaults to AutoIdentification, in this case showing the Name field. You can see the fields added from the field group by expanding the control.

Creating a custom lookup for reference group controls

The ReferenceGroup form controls are different from other bound form controls in that they allow us to see fields from the referenced table. They do this by creating a reference data source as an outer join. This happens behind the scenes and is constructed using the table relation.

To use a custom lookup with reference group fields, we override the lookupReference method. The structure of this method is as follows:

public Common lookupReference(FormReferenceControl
                                     _formReferenceControl)

By investigating how Microsoft made the lookup work on the DirPartyEcoResCategory form, we see that they overrode the lookupReference and resolveReference methods. Since the category hierarchy is a complicated structure, Microsoft has provided a helper class for us.

For the lookup, the key method is EcoResCategory::lookupCategoryHierarchy. This requires a form control and a hierarchy to select from. Dynamics AX has the ability to have many types of hierarchies. We want the user to see those in the procurement category. This is done by locating the EcoResCategoryHierarchy for the EcoResCategoryNamedHierarchyRole::Procurement role.

To write the lookup method, follow these steps:

  1. We are about to modify the VendParameters form, so we drag this from the AOT to the Forms node of our project.
  2. On the VendParameters form, go to Data Sources | VendParameters | Fields | ConFMSServiceCategoryId | Methods.

    Note

    If you can't see this field, it means the form element was cached. Right-click on the form and choose Restore.

  3. Right-click on the Methods node of this data source field and go to Override method | lookupReference.
  4. Change the method as follows:
    public Common lookupReference(FormReferenceControl
                                     _formReferenceControl)
    {
      Common category;
      EcoResCategoryHierarchy     hierarchy;
      EcoResCategoryHierarchyRole role;
    
      select firstonly * from hierarchy
        join role
          where hierarchy.RecId == role.CategoryHierarchy &&
                role.NamedCategoryHierarchyRole == 
                 EcoResCategoryNamedHierarchyRole::Procurement;
    
      category = EcoResCategory::lookupCategoryHierarchy(
                                         _formReferenceControl,
                                         hierarchy);
    
      return category;
    }
  5. Save the form and try the drop-down list again. We get a nice tree view as expected.

The resolveReference method is triggered if we enter a category manually. If we enter the VEHICLES category, we want to ensure that it finds a reference within the procurement hierarchy.

The code is actually similar; Microsoft provides a helper function to resolve the reference, and we have to supply the form control and the EcoResHierarchy record for the procurement role. The code we need is as follows:

public Common resolveReference(FormReferenceControl
                                           _formReferenceControl)
{
    Common category;
    EcoResCategoryHierarchy     hierarchy;
    EcoResCategoryHierarchyRole role;

    select firstonly * from hierarchy
        join role
            where hierarchy.RecId == role.CategoryHierarchy
               && role.NamedCategoryHierarchyRole ==
                  EcoResCategoryNamedHierarchyRole::Procurement;

    category = EcoResCategory::resolveCategoryHierarchy(
                               _formReferenceControl, hierarchy);

    return category.RecId ? category : null;
}

The last line introduces a new command—the inline if statement. This is similar to C#, and is defined as follows:

<boolean condition> ? <statement if true> : <statement if false>

We can now enter values directly and be sure that the reference is resolved and the correct value is set in the table.

Creating a custom lookup for standard fields

Most of the time, the lookups we would create are based on string controls, such as the item lookup on the sales order line. A lookup on form control is performed by the control's lookup method. When a form control is bound to a data source, it calls the data source field's lookup method. This is where we would make the change to alter the lookup.

The following example is from the InventTable.lookupItem table:

public client static void lookupItem(FormStringControl _ctrl)
{
  SysTableLookup sysTableLookup = SysTableLookup::newParameters(
                                   tableNum(InventTable),_ctrl);
  Query           query = new Query();
  QueryBuildDataSource queryBuildDataSource = 
                     query.addDataSource(tableNum(InventTable));

  sysTableLookup.addLookupField(fieldNum(InventTable,ItemId));
  sysTableLookup.addLookupMethod(tableMethodStr(
                               InventTable,defaultProductName));
  sysTableLookup.addLookupMethod(tableMethodStr(
                                      InventTable,itemGroupId));
  sysTableLookup.addLookupField(fieldNum(
                                        InventTable,NameAlias));
  sysTableLookup.addLookupField(fieldNum(InventTable,ItemType));
    sysTableLookup.addSelectionField(fieldNum(
                                          InventTable,Product));

  sysTableLookup.parmQuery(query);
  sysTableLookup.performFormLookup();
}

A lookup method is broken down as follows:

  1. Construct the SysTableLookup class with a primary data source and a form control.
  2. Construct a query and add a primary data source.
  3. Add further child data sources and ranges as required.
  4. Add the required fields and display methods to the SysTableLookup object.
  5. Pass the constructed query object to the SysTableLookup object and tell it to perform the lookup.

To use this code, we would simply override the data source field's lookup method, as follows:

public void lookup(FormControl _formControl, str _filterStr)
{
    InventTable::lookupItem(_formControl);
}

You may notice that we aren't handling a return from the lookup method. The lookup class updates the form control for us if the user selects a record.

Adding a method to state whether this is a vehicle service provider or not

Using the procurement category not only provides the user with a method of identifying vendors as vehicle service providers, but also works with a standard process. We also have a minimal footprint on standard code.

This does present one problem, however; it is difficult to determine whether the vendor is a vehicle service provider. In order to solve this, VendTable should have a method that returns information on whether it is a vehicle service provider or not. This method sits naturally on the vehicle table, so it can be used wherever necessary.

We will write this method as a display method, so we can add it to a form or info part, if desired. Since we intend to use field groups wherever possible, we will create an EDT for the return type; in this way, a label will be added automatically.

Create the ConFMSIsVehicleServiceProvider EDT as a type enum, extending NoYesId and with the Vehicle service provider label.

Using personalization on the Vendor Categories form, we can determine that the list of categories a supplier is associated with is in the VendCategory table. We need a way to work out whether this category is a descendant of the Vehicle service provider category in vendor parameters.

Since this structure is complicated, we would expect a helper function to exist. The way to find such a function would be to first look at the EcoResCategory table, and then look for a class that provides this functionality. The EcoResCategory table has a method called getAscendants. This returns a set of records that are parents of the current category record.

We should test this first, to check whether it works for us. We will write a job that shows all ascendants of the Cars category. We are expecting to see VEHICLES and CORP PROCUREMENT CATEGORIES.

Since the getAscendants method returns a set of records, we will use a new method to iterate through them. The job is as follows:

static void ConFMSTestCategoryHierarchy(Args _args)
{
    EcoResCategory ascendants, child;

    select child where child.Name == "Cars";

    ascendants = child.getAscendants(false);
    while(ascendants.RecId != 0)
    {
        info(ascendants.Name);
        next ascendants;
    }
}

The next command will select the next record in the set of records returned by the getAscendants method, and the while command will execute as long as there is an active record in ascendants. The result should look like what is shown in the following screenshot:

Adding a method to state whether this is a vehicle service provider or not

Now that we know that the method works, we can write our display method. Create a new method on the VendTable table and write the following lines of code:

public display ConFMSIsVehicleServiceProvider conFMSDispIsVehicleServiceProvider()
{
    EcoResCategoryHierarchy     hierarchy;
    EcoResCategoryHierarchyRole role;
    VendCategory                vendCategories;
    EcoResCategory              ascendantCategories, category;
    VendParameters              parm;
    EcoResCategoryId            serviceCategoryId;

    parm = VendParameters::find();
    serviceCategoryId = parm.ConFMSServiceCategoryId;
    // if there is not category, return No now.
    if(serviceCategoryId == 0)
    {
        return NoYes::No;
    }

    select firstonly * from hierarchy
        join role
            where hierarchy.RecId == role.CategoryHierarchy
               && role.NamedCategoryHierarchyRole ==
                  EcoResCategoryNamedHierarchyRole::Procurement;
    // if a procurement hierarchy has not been
    //  configured, return No now
    if(hierarchy.RecId == 0)
    {
        return NoYes::No;
    }

    while select vendCategories
        where vendCategories.VendorAccount == this.AccountNum &&
              vendCategories.VendorDataArea == this.dataAreaId
        join category
            where category.RecId == vendCategories.Category &&
                  category.CategoryHierarchy == hierarchy.RecId
    {
        // Check the current level
        if(category.RecId == serviceCategoryId)
        {
            return NoYes::Yes;
        }
        // get all parent category records for the
        // current category
        ascendantCategories = category.getAscendants();
        while(ascendantCategories.RecId != 0)
        {
            if(ascendantCategories.RecId == serviceCategoryId)
            {
                return NoYes::Yes;
            }
        }
    }
    return NoYes::No;
}

Note

It may be tempting to add this to the Vendor list page. This goes against best practice, as the method will execute for each row on the list page, affecting user experience.

Since the EDT extends NoYesId, we return its base enum value of NoYes::No or NoYes::Yes.

Using event handlers to reduce footprint on standard AX

Our organization needs us to provide a method to allow reporting on purchase orders for vehicle service providers. The solution we have decided upon is to record against a purchase order whether or not the order was a vehicle service order. The order should default as a vehicle service order if the supplier is a vehicle service provider, and allow the user to uncheck this flag. Orders that are not for vehicle service providers should not be allowed to have this flag set.

This provides the opportunity to demonstrate event handling, which allows us to hook into AX functionality with minimum footprint. We will also cover some of the tables used in the purchase order process.

First, the requirement to "record against a purchase order" is there to highlight common mistakes. Although fields on a purchase order can be used for reporting purposes, they shouldn't be. They should only be used to help manage the order-to-invoice process. Once invoiced, the purchase order can be deleted, and many organizations routinely delete invoiced purchase orders, either at the point of invoice or after a period of time.

To be used for reporting, we need this information to be stored somewhere more suitable, and this depends on why we need it. If it is for statistical reporting, it should be on the invoice journal record. Dynamics AX follows standard naming conventions for both sales and purchase orders, and the documents they generate fall under the same framework—FormLetter.

Each time a document, such as a purchase order invoice, is posted, it creates a set of records in a journal set. This allows the document to be reproduced as it was when first printed, and allows reprint on the invoice even if the order has been deleted.

These tables follow a naming convention: <module><document> followed by Jour for the header and Trans for the lines. The prefix for vendor documents is Vend and for customer documents is Cust. The following table shows the standard journal tables:

Module

Document

Journal tables

Cust

Confirmation

CustConfirmJour

CustConfirmTrans

Vend

Confirmation

VendPurchOrderJour

Cust

Packing slip

CustPackingSlipJour

CustPackingSlipTrans

Vend

Packing slip

VendPackingSlipJour

VendPackingSlipTrans

Cust

Invoice

CustInvoiceJour

CustInvoiceTrans

Vend

Invoice

VendInvoiceJour

VendInvoiceTrans

In our case, it is statistical reporting, so this should go against VendInvoiceJour. This was determined using the Personalization form from the Invoice journal form.

The first step is to flag the order as a vehicle service order and allow the user to uncheck this. We will do this by adding a field to PurchTable, but we will handle the various events using event handlers.

To do this, follow these steps:

  1. Create a new EDT called ConFMSVehicleServiceOrder of the enum type. It extends NoYesId. Set the label to Vehicle service order.

    Note

    Although we could just use the NoYesId EDT directly and set a label on the field, creating a new EDT adds benefits beyond property reuse. When used in a display method, EDT sets the label on the resulting form control.

  2. Drag the PurchTable table into your project.
  3. Drag the new EDT onto the Fields node on PurchTable.
  4. Save the table, open the Fields node in a new window, and drag the new field onto the Administration field group.

    Note

    We would determine that this is the correct field group on the create form using the personalization technique.

  5. Save the changes. You can test the field displays by creating a new purchase order.
  6. To avoid modifying standard code, we will write a class that handles the events we are interested in. This class will have a specific purpose, so create a new class named ConFMSPurchVehicleOrderHandler.

    Note

    This method means that we will have one or more event subscriptions, which is perfect in our situation—it is a new module and allows it to be installed without any dependency on other customer elements. Sometimes, when making more generalized modifications, it is more convenient to have one class that handles the table or class, for example, ConPurchTableHandler.

  7. In this class, we need to handle the initFromVendTable and validateField table methods, as we need to default our new fields and verify that it isn't set for vendors who aren't vehicle service providers. Right-click on the new class and go to New | Pre-post event handler.
  8. Rename the method to initFromVendTableEventHandler.
  9. We have written an event handler before, but this time, we need to get the VendTable record being passed to it. On inspection, we notice that the method has a _vendTable parameter, which we will use. The following lines of code will get the VendTable record, check whether the vendor is a vehicle service provider, and set our new field accordingly:
    public static void initFromVendTableEventHandler(XppPrePostArgs _args)
    {
        VendTable vendTable;
        PurchTable purchTable = _args.getThis();
    
        if(!_args.existsArg("_vendTable"))
            throw error(strFmt("@SYS22828",
                        staticMethodStr(
                              ConFMSPurchVehicleOrderHandler,
                              initFromVendTableEventHandler)));
        vendTable = _args.getArg("_vendTable");
    
        purchTable.ConFMSVehicleServiceOrder = NoYes::No;
        if(vendTable.conFMSDispIsVehicleServiceProvider() == 
                                                    NoYes::Yes)
        {
            purchTable.ConFMSVehicleServiceOrder = NoYes::Yes;
        }
    }

    Note

    The staticMethodStr method validates at compile time that the static method exists, and returns the method name. This is for convenience if we later decide to rename the method. The @SYS22828 label references the Function %1 has been incorrectly called label text.

  10. Next, we need to write a method to handle the validateField method on PurchTable so that we allow the field to be set only if the supplier is a vehicle service supplier. This time, we need to read the _fieldToCheck parameter and alter the return value of the method. This is done by the following lines of code:
    public static void validateFieldEventHandler(XppPrePostArgs _args)
    {
        VendTable vendTable;
        PurchTable purchTable = _args.getThis();
        RefFieldId fieldToCheck;
        boolean ret = _args.getReturnValue();
        // no point checking further if the validation 
        // has already failed.
        if(!ret)
            return;
    
        if(!_args.existsArg("_fieldIdToCheck"))
        {
            throw error(strFmt("@SYS22828",
                    staticMethodStr(ConFMSPurchVehicleOrderHandler,
                                    initFromVendTableEventHandler)));
        }
        fieldToCheck = _args.existsArg("_fieldIdToCheck");
        switch(fieldToCheck)
        {
            case fieldNum(PurchTable, ConFMSVehicleServiceOrder):
                vendTable = purchTable.vendTable_OrderAccount();
                if(vendTable.conFMSDispIsVehicleServiceProvider() &&
                   purchTable.ConFMSVehicleServiceOrder == NoYes::Yes)
                {
                    ret = checkFailed(strFmt(
                      "Supplier %1 is not a vehicle service supplier",
                      purchTable.OrderAccount));
                }
                break;
        }
        if(!ret)
        {
            _args.setReturnValue(ret);
        }
    }
  11. Finally, we need to add event handlers to the PurchTable's initFromVendTable and validateField methods.
  12. In the PurchTable table, expand Methods, right-click on the initFromVendTable method, and select New Event Handler Subscription.
  13. On the property sheet for the new event subscription, fill in the fields as per the following screenshot. Post is selected because we want our code to run after the method completes.
    Using event handlers to reduce footprint on standard AX
  14. Next, add an event subscription to validateField and configure it as follows:
    Using event handlers to reduce footprint on standard AX
  15. Again, the CalledWhen property is Post, otherwise the validateField method would have the final say on the return value.

We should test this now by creating a new order for a supplier that is a vehicle service provider, and one order for a supplier that isn't.

The next stage is to add our field to the VendInvoiceJour table and set the field based on the PurchTable record that it is created from. This is done by the following steps:

  1. Drag the VendInvoiceJour table into the project. Then add a new field from the EDT just as you did for PurchTable.
  2. For tables that take values as defaults from a source table, we expect that there will be a method that will initialize values from the source table. In this case, it is initFromPurchTable.
  3. Rather than modify the initFromPurchTable method, we will add an event handler on the ConFMSPurchVehicleOrderHandler class, and create a new pre-post event handler method called vendJourInitFromPurchTableEventHandler.
  4. We need to get the get the VendInvoiceJour record and set our new field based on the method's _purchTable parameter, which is done by the following lines of code:
    public static void vendJourInitFromPTEventHandler(XppPrePostArgs _args)
    {
        PurchTable purchTable;
        VendInvoiceJour vendInvoiceJour = _args.getThis();
    
        if(!_args.existsArg("_purchTable"))
        {
            throw error(strFmt("@SYS22828",
                    staticMethodStr(ConFMSPurchVehicleOrderHandler,
                                    vendJourInitFromPurchTableEventHandler)));
        }
        purchTable = _args.getArg("_purchTable");
        vendInvoiceJour.ConFMSVehicleServiceOrder = 
                                purchTable.ConFMSVehicleServiceOrder;
    }

    Note

    The VendInvoiceJour table is passed by reference, so there is no need to send it back. We must not call update in this case because initFrom methods are often called before the record is inserted, and we would interfere with the normal execution of the code.

  5. Add the event handler to the VendInvoiceJour.initFromPurchTable method as before.

You may notice on occasion that the drop-down list for the method is empty. AX uses a pattern to recognize event handler methods, and sometimes, this fails to execute correctly. This can be due to the method name length or compilation errors in the code. You can manually paste the method name, as the important parameter is AOTLink, which is set by the system based on the Class and Method parameters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.79.147