16 min read

If you have come this far, you are most likely ready to figure out how to change or add functionality in standard AX. This article, written by Mohammed Rasheed, the author of Learning MS Dynamics AX 2012 Programming, takes you through some of the challenges you will face when trying to modify how some of the standard modules in AX behave.

We will look at some typical problems that you might face when integrating to standard AX in the main modules of Dynamics AX:

  • Inventory
  • Ledger

(For more resources related to this topic, see here.)

The inventory module

The inventory module in AX contains the storage and setup of items and explains how they are stored in the physical inventory. It is also heavily integrated to most of the other modules; the production module has to know which items to produce and which items the production consists of. To create a sales order in the Accounts Receivable module, you need to know which items to sell and so on.

The InventTable entity schema

The main entity of the inventory, as you have figured out probably, is the Product Information Management module. There are two main concepts that you need to understand—the concept of product and item.

A product can be considered to be a definition (dare I call it a template) of an item. The product table is a shared table (which means it is not company specific) and it holds basic details about the product. A product is then released into a company where it becomes an item (also referred to as Released product). The following two diagrams published by Microsoft illustrate the product data structure and key relations:

Not all fields have been captured in these diagrams.

Learning MS Dynamics AX 2012 Programming

The following table gives a brief description of each of the tables shown in the preceding InventTable entity schema:

Table name

Description

EcoResProduct

This is a base table for the inherited tables that follow next.

EcoResProductMaster

This table holds information on products that can have variants (color, size, and so on.). Within the application, these products are referred to as product masters.

EcoResDistinctProduct

A distinct product is effectively a product that does not have variants (it is not associated with inventory dimensions color, size, and so on).

InventTable

This table contains information about the released items.

InventItemGroup

This table contains information about item groups.

InventDimGroup

This table contains information about a dimension group.

InventTableModule

This table contains information about purchase, sales and inventory specific settings for items.

InventModelGroup

This table contains information about inventory model groups.

InventItemSalesSetup

This table contains default settings for items, such as site and warehouse. The values are related to sales settings.

InventItemPurchSetup

This table contains default settings for items, such as site and warehouse. The values are related to purchase settings.

InventItemInventSetup

This table contains default settings for items, such as site and warehouse. The values are related to inventory settings.

InventItemLocation

This table contains information about items and related warehouse, and counting settings. The settings can be made specific based on the items configuration and vary from warehouse to warehouse.

InventDim

This table contains values for inventory dimensions.

The table description in the list below each entity schema in this article is taken from the Dynamics AX SDK, also known as the Developer Help.

As you can see, this diagram is very simplified as I have left out most of the fields in the tables to make the preceding diagram more readable. The purpose of the diagram is to give you an idea of the relations between these tables.

The diagram shows that items have tables linked to them that describe their behavior in purchase, inventory and sales, and how the items should be treated with regards to financial posting and inventory posting.

The InventTrans entity schema

The InventTransaction schema is another important in the inventory module. Again, this is very simplified with only the main entities and the fields that make up the primary and foreign keys, as shown in the diagram from a Microsoft whitepaper

Learning MS Dynamics AX 2012 Programming

The following table gives a brief description of each of the tables shown in the preceding InventTrans entity schema:

Table name

Description

InventTransOrigin

This was a new table added in AX 2012 and is effectively used to join InventTrans table to the origination (sales line, purchase lines, inventory adjustment, and so on) transaction line via the InventTransOrigin tables.

InventTransOrigin tables

There are multiple tables such as InventTransOriginSalesline and InventTransOriginPurchLine, and they are fundamentally used to link the transaction origin document to an inventory transaction record.

InventTrans

This table contains information about inventory transactions. When order lines, such as sales order lines or purchase order lines are created, they generate related records in this table. These records represent the flow of material that goes in and out of the inventory.

InventTable

This table contains information about inventory items.

InventSum

This table contains information about the present and expected on-hand stock of items. Expected on-hand stock is calculated by looking at the present on-hand values and adding whatever is on order (has been purchased but not arrived yet).

InventDim

This table contains values of inventory dimensions.

VendTable

This table contains information on vendors for Accounts Payable.

CustTable

This table contains the list of customers for Accounts Receivable and customer relationship management.

Understanding main class hierarchies

In addition to the entity schemas, it is important to know a little about the main class hierarchies within the Inventory module. We use the classes that the main class hierarchies consist of to validate, prepare, create, and change transactions.

The InventMovement classes

The InventMovement classes are used to validate and prepare data that will be used to generate inventory transactions. The super class in the hierarchy is the abstract class called InventMovement. All of the other classes in the hierarchy are prefixed InventMov_.

For example, InventMov_Sales is used to validate and prepare inventory with sales line transactions, or InventMov_Transfer, which is used when dealing with inventory transfer journals.

The following figure is taken from the type hierarchy browser of the InventMovement class and shows the whole class hierarchy under InventMovement:

Learning MS Dynamics AX 2012 Programming

The InventUpdate classes

The InventUpdate classes are used to insert and update inventory transactions. Whenever a transaction should be posted, the updateNow() method in the correct InventUpdate subclass will execute. The super class in the hierarchy is the InventUpdate class and the other classes in the hierarchy are prefixed InventUpd_.

For example, InventUpd_Estimated is used whenever an item line is entered in the system that will most likely generate a physical transaction in future. This can be, for example, a sales order line that has the on order sales status. When a line like this is entered or generated in AX, the InventUpd_Estimated class is triggered so that the inventory transactions will reflect that the item is on order.

The following figure shows the type hierarchy browser of the InventUpdate class and its subclasses:

Learning MS Dynamics AX 2012 Programming

The InventAdj classes

Whenever an adjustment to an inventory transaction takes place, the InventAdj classes are used. The adjustments typically occur when you are closing the inventory.

The following figure shows the application hierarchy tree of the InventAdj class and its subclasses:

Learning MS Dynamics AX 2012 Programming

The InventSum classes

The InventSum classes are used to find the on-hand information about a certain item at a certain date. The InventOnHand class is used to find the current on-hand information. The InventSum classes are not structured in a hierarchy as the previously mentioned classes.

Now that you have seen how some of the main tables in the inventory module are related to each other, we will discuss how to write code that integrates with the inventory module.

Working with inventory dimensions

One thing that you will have to learn sooner rather than later is how the inventory dimensions work. Basically, the InventDim table holds information about all the different dimensions that are related to an item. These dimensions can be divided into three types: item, storage, and tracking dimensions. By default, AX comes with the following dimensions:

  • Four item dimension: The dimensions are Color, Size, Style, and Configuration
  • Six storage dimension: The dimensions are Site, Warehouse, Location, Pallet, Inventory status, and License plate
  • Five tracking dimension: The dimensions are Batch, Serial, Owner, Inventory Profile, and GTD number

The number of dimensions visible within the application will vary based on the configuration keys enabled.

Finding an inventory dimension

When combinations of these dimensions are needed in a journal or a transaction, we always check to see whether the combination already exists. If it does, we link to that record in the InventDim table. If it doesn’t exist, a new record is created in the InventDim table with the different dimensions filled out. Then, we can link to the new record. This is done by using a method called findOrCreate from the InventDim table. You can see how to use it in the following code:

static void findingDimension(Args _args)
{
InventDim           inventDim;

// Set the values that you need for
// the journal or transaction
inventDim.InventLocationId = "01";
inventDim.InventColorId = "02";
// Use findOrCreate and use the inventDim
// variable that you have set values into
inventDim = InventDim::findOrCreate(inventDim);
// Use the inventDimId to link to the
// InventDim record that was either
// found or created
info(inventDim.inventDimId);
}

Finding the current on-hand information

Another thing you should know is how to find how many items are available within a certain InventDim scope. You can do this by converting the InventDim fields into an InventDim variable and then specifying which dimension fields or combination of dimension fields you would like to get on-hand information on. In the following example, we search for the on-hand information for an item in a specific color:

static void findingOnHandInfo(Args _args)
{
ItemId              itemId;
InventDim           inventDimCriteria;
InventDimParm       inventDimParm;
InventOnhand       inventOnhand;

// Specify the item to get onhand info on
itemId = "1001";

// initilise inventOnHand
inventOnhand = InventOnhand::newItemId(itemId);

// Specify the dimensions you want
// to filter the onhand info on
inventDimCriteria.InventColorId = "02";
// Set the parameter flags active
// according to which of the dimensions
// in inventDimCriteria that are set
inventDimParm.initFromInventDim(inventDimCriteria);

// Specify the inventDim,
// inventDimParm and itemId
inventOnhand.parmInventDim(inventDimCriteria);
inventOnhand.parmInventDimParm(inventDimParm);
inventOnhand.parmItemId(itemId);

// Retrieve the onhand info
info(strfmt("Available Physical: %1",inventOnhand.availPhysical()));
info(strfmt("On order: %1",inventOnhand.onOrder()));
}

You could easily narrow the information down to a specific warehouse by setting a warehouse (InventLocationId) to the inventDimCriteria method, as we have done for the InventColorId value.

Finding on-hand information by a specific date

The following example will let you find the on-hand quantity information of a specific item with a specific color (inventory dimension) at a specific date. The code is as follows:

static void findingOnHandByDate(Args _args)
{
ItemId             itemId;
InventDim           inventDimCriteria;
InventDimParm       inventDimParm;
InventSumDateDim   inventSumDateDim;
 
// Specify the item to get onhand info on
itemId = "1001";
// Specify the dimensions you want
// to filter the onhand info on
inventDimCriteria.InventColorId = "02";
// Set the parameter flags active
// accoring to which of the dimensions
// in inventDimCriteria that are set
inventDimParm.initFromInventDim(inventDimCriteria);
 
// Specify the transaction date, inventDimCriteria,
// inventDimParm and itemId to receive a new object
// of InventSumDateDim
 inventSumDateDim = 
InventSumDateDim::newParameters(mkdate(01,01,2014),
itemId, inventDimCriteria, inventDimParm);
// Retrieve the on hand info using the methods // of InventSumDateDim info(strfmt("PostedQty: %1",inventSumDateDim.postedQty())); info(strfmt("DeductedQty: %1",inventSumDateDim.deductedQty())); info(strfmt("ReceivedQty: %1",inventSumDateDim.receivedQty())); }

Entering and posting an inventory journal from code

One of the things you will need to know when dealing with the inventory module is how to automate journal entry and posting. This example will show you one way of doing this. This method is typically used when performing data migration. The code is as follows:

static void enterPostInventJournal(Args _args)
{
InventJournalName       inventJournalName;
InventJournalTable      inventJournalTable;
InventJournalTrans     inventJournalTrans;
 
InventJournalTableData inventJournalTableData;
InventJournalTransData inventJournalTransData;
 
InventTable             inventTable;
InventDim               inventDim;
 
select firstonly inventTable; // find item
 
// find a movement journal name
select firstOnly inventJournalName
where inventJournalName.JournalType ==
InventJournalType::Movement;   // Initialize the values in the inventJournalTable // from the inventJournalName and insert the record inventJournalTable.clear(); inventJournalTable.initValue(); inventJournalTable.initFromInventJournalName
(inventJournalName); inventJournalTable.insert();   inventJournalTableData =
JournalTableData::newTable(inventJournalTable);   // Insert to records into the inventJournalTrans table inventJournalTrans.clear(); inventJournalTrans.initFromInventJournalTable
(inventJournalTable); inventJournalTrans.TransDate = systemdateget(); inventJournalTrans.initFromInventTable(inventTable); inventJournalTrans.Qty = 3;   // Find the default dimension inventDim.initFromInventTable
(inventJournalTrans.inventMovement().inventTable(),
InventItemOrderSetupType::Invent,inventDim); // Set additional mandatory dimensions - based on item
selected //inventDim.InventColorId     = "02"; //inventDim.InventSizeId     = "50"; //inventDim.configId         = "HD"; // See if the inventDim with the selected values // allready exist in the InventDim table. If not, it's // created automatically   inventDim = InventDim::findOrCreate(inventDim); inventJournalTrans.InventDimId = inventDim.inventDimId;   inventJournalTransData =
inventJournalTableData.journalStatic()
.newJournalTransData(inventJournalTrans,
inventJournalTableData); inventJournalTransData.create();   // Use the InventJournalCheckPost class to    // post the journal if (InventJournalCheckPost::newPostJournal
(inventJournalTable).validate()) InventJournalCheckPost::newPostJournal (inventJournalTable).run(); }

The Ledger module

The Ledger module (also known as general ledger) is where all financial transaction in AX is controlled and stored. All of the other modules are connected to the Ledger module in some way as it is the money central of AX.

The main entity of the Ledger module is the Ledger table that consists of the chart of accounts. In addition, there are transaction tables and other tables related to the Ledger table and, hopefully, you will understand how some of these tables relate to each other by taking a look at the following entity schema from Microsoft:

Learning MS Dynamics AX 2012 Programming

The following table gives a brief description of each of the tables shown in the Ledger entity schema in the preceding diagram:

Table name

Description

Ledger

This table contains the definitions of the general ledger accounts.

GeneralJournalAccountEntry

This table contains information about the general ledger entry.

GeneralJournalEntry

This table contains transaction metadata such as posting layer, transaction date, and so on.

SubledgerVoucherGeneralJournalEntry

This table links the subledger voucher to the general ledger entry.

LedgerEntry

This table contains general ledger bank transaction information.

Posting ledger transactions

There are two ways of posting ledger transactions:

  • Ledger Journal: Enter data into a journal and post the journal using the LedgerJournalCheckPost class.
  • Ledger Voucher: Create a list of vouchers (LedgerVoucherObject) and post them using the LedgerVoucher class.

Entering and posting a LedgerJournal class

The easiest way to post a ledger transaction is using the LedgerJournal class. This is similar to the example in the previous section of this article, where you learned how to fill the inventory journal with data and then post the journal from code. The code is as follows:

In the following example, we will post a ledger transaction by filling LedgerJournalTable and LedgerJournalTrans with data and post the journal using the LedgerJournalCheckPost class:

static void enterPostLedgerJournal(Args _args)
{
LedgerJournalName       ledgerJournalName;
LedgerJournalTable     ledgerJournalTable;
LedgerJournalTrans     ledgerJournalTrans;
 
LedgerJournalCheckPost ledgerJournalCheckPost;
container               ledgerDimensions, offsetDimensions;
Voucher                 voucher;
 
// Set ledger account information
ledgerDimensions = ["22322","Chicago"];
offsetDimensions = ["22321","London"];
 
// You MUST have tts around the code or else
// the numberSeq will generate an error while
// trying to find the next available voucher
ttsbegin;
 
// Specify which ledger journal to use
ledgerJournalName = LedgerJournalName::find("GenJrn");
// Initialize the values in the ledgerJournalTable
// from the ledgerJournalName and insert the record
 ledgerJournalTable.initFromLedgerJournalName
(ledgerJournalName.JournalName);
ledgerJournalTable.insert(); // Find the next available voucher number. voucher = new JournalVoucherNum
(JournalTableData::newTable(ledgerJournalTable)).getNew(false);
  ledgerJournalTrans.voucher = voucher; ledgerJournalTrans.initValue();   // Set the fields necessary in the ledgerJournalTrans ledgerJournalTrans.JournalNum     =
ledgerJournalTable.JournalNum; ledgerJournalTrans.currencyCode    = 'USD'; ledgerJournalTrans.ExchRate         =
Currency::exchRate(ledgerJournalTrans.currencyCode); ledgerJournalTrans.LedgerDimension =
AxdDimensionUtil::getLedgerAccountId(ledgerDimensions); ledgerJournalTrans.AccountType     =
ledgerJournalACType::Ledger; ledgerJournalTrans.AmountCurDebit   = 1220.00; ledgerJournalTrans.TransDate       = systemdateget(); ledgerJournalTrans.Txt             = 'Transfer to UK'; ledgerJournalTrans.OffsetDefaultDimension =
AxdDimensionUtil::getLedgerAccountId(offsetDimensions); // Insert to records into the ledgerJournalTrans table ledgerJournalTrans.insert();   // Use the LedgerJournalCheckPost class to // post the journal ledgerJournalCheckPost =
LedgerJournalCheckPost::construct(LedgerJournalType::Daily); // Set the JournalId, tableId of the journalTable // and specify to post the journal (not only check it). ledgerJournalCheckPost.parmJournalNum
(ledgerJournalTable.JournalNum); ledgerJournalCheckPost.parmPost(NoYes::Yes); ledgerJournalCheckPost.run();   ttscommit; }

You can also have AX check and validate the content of the journal before posting it. This can be a good idea, especially when the data originates from a different solution—for instance, when migrating data from the previous system and into AX.

Entering and posting a LedgerVoucher class

Using the LedgerVoucher classes to post ledger transactions means that you first create vouchers and then post them. This is more controlled and similar to the traditional way of dealing with posting of ledger transactions.

The method is based on having a LedgerVoucher object where you can add multiple vouchers (LedgerVoucherObject). For each voucher, you typically have two transactions (LedgerVoucherTransObject), one for debit and one for credit. You can of course have more transactions in a voucher, but they have to be compatible. This means that if you add the amount of all of the credit transactions and all of the debit transactions, the result has to be 0.

When all transactions have been added to a voucher and all vouchers have been added to the LedgerVoucher object, you can simply call the ledgerVoucher.end() method in order to validate and post the voucher. The code is as follows:

static void createAndPostLedgerVoucher(Args _args)
{
AxLedgerJournalTable       ledgerJournalTable;
AxLedgerJournalTrans       ledgerJournalTrans;
 
container ledgerDimensions, offsetDimensions;
 
// Set ledger account information
ledgerDimensions = ["22322","Chicago"];
offsetDimensions = ["22321","London"];
 
ledgerJournalTable         = new AxLedgerJournalTable();
ledgerJournalTrans         = new AxLedgerJournalTrans();
 
ledgerJournalTable.parmJournalName("Gen");
ledgerJournalTable.save();
 
ledgerJournalTrans.parmAccountType(LedgerJournalACType::Ledger);
ledgerJournalTrans.parmJournalNum
(ledgerJournalTable.ledgerJournalTable().JournalNum);   ledgerJournalTrans.parmLedgerDimension
(AxdDimensionUtil::getLedgerAccountId(ledgerDimensions)); ledgerJournalTrans.parmAmountCurDebit(500);   ledgerJournalTrans.parmOffsetLedgerDimension
(AxdDimensionUtil::getLedgerAccountId(offsetDimensions));   ledgerJournalTrans.end(); }

Summary

In this article, you have seen how to trigger the standard functionality in AX, which is normally done manually using a code. Being able to automate processes and extend the standard functionality will most likely be something that you will do over and over again if you work with customer development.

You should now know how the main tables and the transactional tables in the inventory and Ledger are related to each other.

In the inventory section, you learned how to find an inventory dimension, how to find the current inventory stock for an item, how to find the inventory stock for an item by a specific date, and how to enter and post an inventory journal.

In the The Ledger module section, you learned how to use financial dimensions in code and how to enter and post a ledger journal.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here