Thursday, September 6, 2012

Production order status reset (from code vs. form)

Recently I had the need to change the status of an order back to "Created" programmatically. I found lots of blogs about how to go forward with the status, but going back was hard to find. Here's what I ended up doing. I wrote a class with 3 methods. A classdeclaration() with a prodid variable (used below), a parmProdId method and the following method:

boolean resetStatusToCreated
{
    ProdMultiStatusDecrease  prodMultiStatusDecrease;
    ProdParmStatusDecrease   prodParmStatusDecrease;
    ProdTable               prodTable;
    Args                    args = new Args();
    ;
    select prodTable where prodTable.ProdId == prodID;
    args.record(prodTable);
   
    prodParmStatusDecrease.clear();
    prodParmStatusDecrease.initFromProdTable(prodTable);
    prodParmStatusDecrease.WantedStatus = ProdStatus::Created;
    prodParmStatusDecrease.ParmId = NumberSeq::newGetNum(CompanyInfo::numRefParmId()).num();
    prodParmStatusDecrease.insert();

    prodMultiStatusDecrease = prodMultiStatusDecrease::construct(args);
    prodMultiStatusDecrease.initParmBuffer(prodParmStatusDecrease);
    prodMultiStatusDecrease.parmId(prodParmStatusDecrease.ParmId);
    prodMultiStatusDecrease.run();
   
    select prodTable where prodTable.ProdId == prodId;
    if(prodTable.ProdStatus == prodStatus::Created)
    {
        return true;
    }
    return false;

It seems to have worked. I just have to verify that everything was reversed properly. Testing the code is as easy as doing this:

    ResetProdStatusClass = new ResetClass();
    ResetProdStatusClass.parmProdId('WO000001');
    ResetProdStatusClass.resetStatusToCreated();

I specifically needed to take it back to the Created status, but I imagine you could modify this to pass in the status you wanted it reset to and set it in the WantedStatus field.

Good luck!

Wednesday, August 15, 2012

Using containers vs. other collection classes


I’ve seen containers used on some forms, so I thought this would be useful information.
This is based on my experience and some sites I’ve read (which I’ve given links to below).

If you’re going to store recIds as your datatype in any of these collection classes and you're not on AX 2012 yet, you should define the type like  this (because AX 2012 handles recIds differently, it will be easier for upgrading when you do this):

Declare a dictype variable like this:
DictType    dt = new DictType(extendedTypeNum(recid));
Then use it like this, here are examples for a map and a set:
msgMap = new Map(dt.baseType(),Types::String);
setRecIds = new Set(dt.baseType());

Containers:
Containers are dynamic and have no limits. They can contain elements of almost all data types: boolean, integer, real, date, string, container, arrays, tables, and extended data types. However, objects may not be stored in containers.

Containers in AX are used very often. It’s easy to work with them, but
data in containers are stored sequentially, and retrieved sequentially. This means that containers provide slower data access if you are working with a large numbers of records. You cannot modify a container in-place, instead each  addition or deletion has to iterate over the entire structure to copy all values into a newly allocated one. So every container manipulation has a run time of O(n). This is why they are not recommended to be used on forms. This site has some good information about how to use containers more efficiently when you do need to use them http://www.axaptapedia.com/index.php?title=Container If you are storing unique data of one type (like storing recids for a checkbox on a form),you can use a Set (see below). If you are storing non-unique data of the same type, use a List (see below).

Maps are most useful if you need a key for your data. I used a map recently to store the recId as a key to save/retrieve the inventTransId of some records. This is a good explanation of how to use Maps (http://www.axaptapedia.com/index.php?title=Map_Class)

Sets:
Sets are an unordered list of items. If you try to add something to a set that is already in the set, it will ignore it. Sets have an in() and remove() method which is useful. This is a good explanation of how to use Sets http://www.axaptapedia.com/index.php?title=Set_Class

Lists:
Lists contain elements that are accessed sequentially. Lists provide getEnumerator() and getIterator() methods (like sets do) which allow you to insert and delete items from the list. Here’s some information about Lists http://msdn.microsoft.com/en-us/library/aa848905%28v=ax.10%29.aspx.

Monday, April 30, 2012

Using the Fill Utility on the table and/or fields you specify

We have a customized field in AX that we want the user to be able to select multiple records and change them all to the same value at once. Dynamics AX 2009 has a fill utility that can do this. However, the fill utility can only be used on tables that are "main" tables (property TableGroup = main). I wanted it to be able to be used on my custom field (a date field) on the SalesTable form. Here's what I had to do:
On the Form: SysRecordInfo, Method: init
Add code to allow the fill utility to be used on my table.
void init()
{
 ....
     // don't do the field level testing if the table doesn't meet the base requirement
    if (dictTable.tableGroup() == TableGroup::Main ||
        common.TableId == tablenum(LedgerJournalTrans)
        //N - GW_Admin_FillUtility - Arains 03/02/2012
        // Allow the fill utility to be used on the SalesTable
     || common.TableId == tablenum(SalesTable))
    {
        element.fillUtilityInit();
    }
    else
    {
        fillGrp.visible(false);
    }
 ....
}

Also, on the Method: fillUtilityInit
Add code to allow the fillUtility to be used for my custom field on the salestable and only my custom field on the SalesTable.

void fillUtilityInit()
{
....
 if (fieldIdLocal == dictTableLocal.primaryKeyField() ||
                        fieldIdLocal == dictTableLocal.fieldName2Id('RECID') ||
                        fieldIdLocal == dictTableLocal.fieldName2Id('DATAAREAID'))
                    {
                        fillGrp.visible(false);
                        return;
                    }

                    // New code for fill utility modifications
                    // If this is the salesTable and this is the custom date field
                    // allow the fill utility to be used, disallows for all other fields
                    if(tblId == TableNum(SalesTable) &&
                       fieldIDLocal != dictTableLocal.fieldName2Id('YourCustomField'))
                    {
                        fillGrp.visible(false);
                        return;
                    }
....
}

Friday, March 30, 2012

Unpicking the entire order all at once

For orders in AX (I am addressing sales orders specifically here), if you want to unpick an order, you have to go to the sales line and, one-by-one, unpick the order. You click on the Inventory button and select the Pick option. In the form, check the autocreate box, then click Post All in the lower area of the form. If you regularly have to pick and unpick orders, especially if you have a lot of lines on your orders, this can become very tedious.

I wrote a job that will unpick the entire order. Now I'm going to put an Unpick button on the sales order form at the order level and allow certain users (security will be used) to do this. I will most likely allow multiple orders to be selected so you can unpick multiple orders with one click of a button. The code for the job I wrote is below:

    // For testing, I set the salesid here.
    // In the final code, I will have to pass in the salesTable record
    // from the salesTable_ds of the form
    SalesId salesid = 'RSO948671';
    TmpInventTransWMS   tmpinventTransWMS;
    InventMovement  movement;
    InventTrans inventTrans;
    salesline   salesline;
    inventtransWMS_pick inventTransPick;
    ;
   
    while select salesline
    where salesline.SalesId == salesId
    {
        select inventTrans
            where inventTrans.TransRefId == salesline.SalesId &&
                  inventTrans.ItemId == salesline.ItemId &&
                  inventTrans.StatusIssue == StatusIssue::Picked;
        if(inventTrans.RecId)
        {
            movement = null;
            movement = InventMovement::construct(salesLine);
            inventTranspick = new InventTransWMS_Pick(movement,tmpInventTransWMS);
            tmpInventTransWMS = null;
            tmpInventTransWMS.initFromInventTrans(inventTrans);
            tmpInventTransWMS.InventQty = inventTrans.StatusIssue == StatusIssue::Picked ? inventTrans.Qty : -inventTrans.Qty;
            tmpInventTransWMS.insert();
            inventTransWMS_pick::updateInvent(inventTransPick, tmpInventTransWMS);
        }
    }

Wednesday, January 11, 2012

Go to main table form dynamic use

You can set the FormRef property on a table to a display menu item that references a class. This will allow you to open different forms depending on some field value in the record that is passed. In the class main() you can retrieve the record and then open the appropriate form from code.