Code highlighting

Showing posts with label Inventory management. Show all posts
Showing posts with label Inventory management. Show all posts

Friday, June 10, 2016

Tutorial: Moving inventory out of a location to complete a put away inbound operation

Introduction

As some of you have probably already read in this blog post, we have recently made some improvements for the inbound flow in Warehouse management, and I wanted to do a quick recording demoing an example which uses some of these capabilities.

For some additional information about the different types of movement flows currently supported please follow to another blog post we've recently published.

Another reason for publishing this particular scenario recording is that we are currently working on another improvement, this time focusing on adding more flexibility for inventory movement operations, where there are work reservations blocking the items in the location from being moved anywhere else. This is a known pain point for a number of customers, and if all goes well, we'll have it addressed in the Fall release of the new Dynamics AX.

The scenario recording is covering one such flow, except for the fact of existing work reservations - in my example there will be none.

I am trying out uploading this to Office Mix, as well as YouTube. Let me know if you like the Office Mix approach - I think it's pretty cool as it makes it possible to browse the recording to a specific point more easily.

Demo





YouTube video:
https://www.youtube.com/watch?v=P7W2Th43-Fo

Question for you

As you can see from the above video, the approach John took is to cancel out of the Put, complete the movement, and then continue the Put.
An alternative could be to complete the put, even though physically the goods are not yet placed into the location, and then create the movement, but in this case we are for a period of time out of sync with the app.

Please leave a comment if the above approach is not a good match for your company, and explain what approach you use instead and why.

Thanks!

Sunday, February 07, 2010

Performance optimization: Deleting inventory journal lines

Preamble

As a developer, you should always consider performance implications of the code you write. In an ERP application like Microsoft Dynamics AX, the main focus should be on query execution, since it takes up the overwhelming part of the servers' resources.

You should always write queries that would execute the minimum amount of time and use the minimum amount of resources, at the same time producing the expected output in all cases.

Note, that performance is one of those things you cannot really verify on a 1-box install with a small test dataset. Most query problems show up only when tested with many users concurrently loading the AOS on a large-size database.

Code example

Table method
AOT\Data Dictionary\Tables\InventJournalTrans\Methods\delete
contains the following code:

if (this.Voucher)
{
if (this.numOfVoucherLines() == 0)
JournalError::deleteVoucher(tablenum(InventJournalTable),this.JournalId,this.Voucher);
}

The code is logically correct, deleting related records from the JournalError table, which contains error messages generated during validation and posting of journals.
But now let us consider the actual implementation. If we rephrase the conditions under which we delete the error message history, it would sound something like:
If a Voucher number is specified on the line being deleted, and there is no more lines in this journal that use this Voucher number, then we remove the JournalError records.

Deeper code analysis for performance

So, let's go in deeper, and open the code for method numOfVoucherLines:

Integer numOfVoucherLines()
{
return any2int((select count(RecId) from inventJournalTrans
where inventJournalTrans.JournalId == this.JournalId &&
inventJournalTrans.Voucher == this.Voucher).RecId);
}

As you can see, it counts all the journal lines with the specified Voucher number and JournalId.
Does it generate the expected output? Yes.
Is it optimal for the scenario in which it is used? No.

In order to determine if JournalError records can be deleted, we only need to know if at least 1 record with the specified Voucher number exists. So why do we need to search and count all such records? We don't.
Note that this code is being executed for each journal line, magnifying the impact of a non-optimal query N-fold.

Suggested solution

So, if we create a new method like this:

boolean voucherLineExist()
{
return (select firstfast firstonly forceplaceholders recId from inventJournalTrans
index hint VoucherIdx
where inventJournalTrans.journalId == this.journalId &&
inventJournalTrans.voucher == this.voucher).recId != 0;
}

and use it instead of numOfVoucherLines() in delete() method, we will greatly improve the overall performance when deleting journal lines.
It has been measured, that the time it takes to delete a journal with a large number of lines (88000, to be specific) has reduced by 6 times, which is fantastic.

Notes

  • forceplaceholders is used in the query explicitly to make sure a query plan is created and reused for this query, which further improves the performance, since the Voucher number most probably changes from line to line (depends on related journal name setup).
  • It is generally still a best practice to avoid creating inventory journals with such a large number of rows, because the journal is usually processed in 1 database transaction.

See also

Thursday, January 28, 2010

Lookup form returning more than one value

I have been posted with the question of creating a lookup that would return more than 1 value into the calling record, filling in a number of fields and populating the selected control with the corresponding value at the same time.

I will try to answer that question by an example from the standard application, particularly using one of the forms owned by my (Inventory) team.

Example


If we take a look at the lookup form for one of the item inventory dimensions (Configuration, Size or Color), we'll see the following tab page on it:
(assuming more than 1 item dimension is enabled for this item)



Basically, the user is able to select the configuration that belongs to a specific dimension combination. Note the check-box "" under the grid. If the user sets that check-box and then selects the configuration from a combination, the values for all item dimensions will be copied to the record from the parent form.
So, basically, the lookup returns more than just the configuration dimension value, but also size and color.

How is that implemented?


The lookup is implemented using a custom form, present in AOT, with name ConfigIdLookup.

The methods important for a working lookup are:

  • init() method.

In particular, the way the calling control is determined based on the args passed into the form:


callerControl = SysTableLookup::getCallerStringControl(element.args());

  • selectMode() method.

(called from setSelectMode() on this form). This method accepts an AX form control as parameter, and informs the kernel about which control should be used as the returning control.

  • closeSelect() method.

This method is executed on lookup forms wheneven a selection of a particular value is made. So, obviously, in this method you still have access you all the fields of the datasource with the selected record, as well as to the calling form through element.args(). Here is the code that handles the update of multiple fields:


if (ctrlTabPageCombination.visible() &&
selectAllCombi &&
ctrlTabPageCombination.isActivePage())
{
// Genericly determine the InventDim datasource on the calling form
callerInventDimDS = inventDimFormSetup.callerInventDimFormDatasource();
if (callerInventDimDS)
{
//...
// Get the current record of that datasource
callerInventDim = callerInventDimDS.cursor();
// Set the needed fields on the record based on the selection in the lookup form
// Basically, this is how you can achieve returning more than one value from a lookup
// Get the record where the values need to be put in, and put them in from the currently selected record
if (inventDimCombination_ConfigId.visible() && inventDimCombination.ConfigId)
callerInventDim.ConfigId = inventDimCombination.ConfigId;
if (inventDimCombination_InventSizeId.visible() && inventDimCombination.InventSizeId)
callerInventDim.InventSizeId = inventDimCombination.InventSizeId;
if (inventDimCombination_InventColorId.visible() && inventDimCombination.InventColorId)
callerInventDim.InventColorId = inventDimCombination.InventColorId;
// Refresh the datasource so that values are displayed on the calling form
callerInventDimDS.refresh();
}
}



If you want to know more


  • Spend some time investigating the existing methods on SysTableLookup class, like the method getCallerStringControl, filterLookupPreRun_DS, filterLookupPostRun.

  • Look at the methods in InventDimCtrl_Frm_Lookup class. They handle all the logic of looking up inventory dimensions, and contain some nice examples of traversing the calling object.

  • Investigate, how the lookup form described above prevents the closing of the form when the check-boxes are clicked or tab pages changed. Hint: take a look at the usage of canSelect form variable