RSS

Category Archives: Dynamics AX

AOSAuthorization property on tables

This property is part of Tables Permission Framework (TPF). The Table Permissions Framework (TPF) enables administrators to add an additional level of security to tables that store sensitive data. TPF adds table-level security that verifies access rights no matter the origin of the request.

To enable TPF, an administrator specifies a value for the AOSAuthorizationProperty on a specific table in the AOT. The AOSAuthorizationProperty authorizes Create, Read, Update, and Delete operations. For some tables, it is important to authorize all operations because the data is sensitive. For other tables, you might find it suitable to specify a subset of operations, such as Create, Update, and Delete. In the case when you have specified a subset, the AOS authorizes the Create, Update, and Delete operations, but allows users to perform View operations if they have access to Microsoft Dynamics AX.

TPF can be enabled on any table in the Microsoft Dynamics AX database. For the sake of time and efficiency, however, administrators assign TPF to tables that are considered to be sensitive or to be of critical business value.

For example, consider the following scenario:

  1. Microsoft Dynamics AX and allows users to access data by using the Microsoft Dynamics AX client, Enterprise Portal, the Application Integration Framework, and a third-party application that connects to Microsoft Dynamics AX by using the .NET Business Connector.
  2. The administrator configured a Microsoft Dynamics AX user group called Senior Accountants, and members of this group have access to sensitive data about financial information and trade secrets. One of the database tables that stores this sensitive information is called FinancialResults. This table was added as part of a customization done by a partner after Microsoft Dynamics AX was installed.
  3. In the Application Object Tree (AOT), the administrator configures the FinancialResults table so that the Application Object Server (AOS) must authorize all operations for that table. The administrator specifies the value CreateReadUpdateDelete for the AOSAuthorizationProperty.
  4. Soon thereafter, a malicious user discovers a vulnerability in Contoso’s third-party application that connects to Microsoft Dynamics AX by using the .NET Business Connector. The malicious user connects to the database as a member of the CRM_users group and attempts to read the data in the FinancialResults table.
  5. Before allowing the read operation, the AOS checks to see if the user is a member of the Senior Leadership user group and if members of the group have permission to read the data. The malicious user is not a member of the Senior Leadership group, so the AOS denies the read operation.

You can change or add TPF for a table, but its is recommended that you perform TPF changes in a test environment so that you can study the impact of TPF changes on user groups that access that table.

To enable TPF on database table:

  1. In the AOT, expand Data Dictionary > Tables.
  2. Right-click a table, and then click Properties.
  3. Click AOSAuthorizationProperty and select a new value by using the drop-down list.
  4. Click Save All.

If you added TPF to a table, you might need to specify or expand permissions for user groups that access that table. You can view which objects access a table by using the Used-by command in the AOT:

  1. In the AOT, expand Data Dictionary > Tables.
  2. Right-click a table, and then click Add-ins > Cross-reference > Update.
  3. Right-click a table, and then click Add-ins > Cross-reference > Used by. The Used by form is displayed. This form shows all objects that access the selected table and what permissions (the Reference column) are required when accessing the table. You might need to adjust user group permissions if you set tighter restrictions on a table.
 
Leave a comment

Posted by on May 5, 2011 in AX 2009, Dynamics AX

 

Iterators Vs. Enumerators

We can traverse our collections by using either an enumerator or an iterator.But there is no clarity why sometimes iterator fails while on other occasions it is flawless.Simply what we do is that just replace iterator with the enumerator.

First theoretically what happens behind the scenes is as follows:

When collection classes were first introduced in DAX, the iterator was the only option.But because of a few unwarranted drawbacks that appear as hard-to-find errors, enumerators were added, and iterators were kept for the backward compatibility. Just see the below listed code snippet

List list   = new List(Types::Integer);
ListIterator  iterator;
ListEnumerator  enumerator;
;
//Populate List
…..
…..

//Traverse using an iterator.
iterator = new ListIterator(list);

while(Iterator.more())
{
print iterator.value());
iterator.next();
}

//Traverse using an enumerator
enumerator = list.getEnumerator();

while(enumerator.moveNext())
{
print enumerator.current();
}

The 1st difference is the way iterator and enumerator instances are created.For the iterator,you call new,and for the enumerator,you get an instance from the collection class by calling the getEnumerator method.

In most cases, both approaches will work equally well. However, when the collection class resides on the opposite tier from the tier on which it is traversed,the situation is quite different.

For example, if the collection resides on the client tier and is traversed on the server tier, the iterator approach fails because the iterator does not support cross-tier referencing.

The enumerator does not support cross-referencing either, but it doesn’t have to because it is instantiated on the same tier as the collection class. Traversing on the server tier using the client tier enumerator is quite network intensive, but the result is logically correct because some code is marked as “Called From”, meaning that it can run on either tier,depending on where it is called from. You could have broken logic if you use iterators, even if you test one execution path.In many cases, hard-to-track bugs such as this surface only when an operation is executed in batch mode.

The 2nd difference is the way traversing happens which is another potential threat as the onus lies on the developer to ensure that he moves the pointer by using .next() method else the code can land into endless loop.So again enumerator is clear winner here

But there is still one scenario while iterator holds edge over enumerator, if you want to delete/insert items from list.See the code snippet below:

List   list = new List(Types::Integer);
ListIterator iterator;
;

list.addEnd(100);
list.addEnd(200);
list.addEnd(300);

iterator = new  ListIterator(list);
while(iterator.more())
{
if(iterator.value() == 200)
  iterator.delete();
iterator.next();
}
print list.toString();   //{100,300}
pause;
}

 
1 Comment

Posted by on May 5, 2011 in AX 2009, Dynamics AX

 

Restoring delete sales order or purchase order

Many people know that when a sales order or purchase order is deleted, it is actually not purged from system like lot of other data but sits in the voided tables. These orders can be viewed from AR -> Inquiries -> History -> Voided sales order or AP -> Inquiries -> History -> Voided sales order.
 
Now from this form you can only view the deleted orders but there is not option of restoring them.
 
Below are some sample code that can be used to restore the voided orders.
 
Sales order restoration:
 
static void restoreDeletedSO(Args _args)
{
    SalesTableDelete    salesTableDelete;
    SalesLineDelete     salesLineDelete;
    SalesTable          salesTable;
    SalesLine           salesLine;
    ;
    SalesTableDelete = SalesTableDelete::find(’00450_036′, true);
    ttsbegin;
    switch (salesTableDelete.Cancelled)
    {
        case Voided::Voided :
            salesTable  = conpeek(salesTableDelete.SalesTable, 1);
            salesTable.insert();
            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);
                salesLine.insert();
            }
            salesTableDelete.delete();
            break;
        case Voided::linesVoided :
            while select forupdate salesLineDelete where salesLineDelete.SalesId == salesTableDelete.SalesId
            {
                salesLine = conpeek(salesLineDelete.SalesLine, 1);
                salesLine.insert();
                salesLineDelete.delete();
            }
            salesTableDelete.delete();
            break;
   }
   ttscommit;
}
Purchase order restoration:
static void restoreDeletedPO(Args _args)
{
    PurchTableDelete    purchTableDelete;
    PurchLineDelete     purchLineDelete;
    PurchTable          purchTable;
    PurchLine           purchLine;
    ;
    purchTableDelete = PurchTableDelete::find(’00242_049′, true);
    ttsbegin;
    switch (purchTableDelete.Cancelled)
    {
        case Voided::Voided :
            purchTable  = conpeek(purchTableDelete.PurchTable, 1);
            purchTable.insert();
            while select forupdate purchLineDelete where purchLineDelete.PurchId == purchTableDelete.PurchId
            {
                purchLine = conpeek(purchLineDelete.PurchLine, 1);
                purchLine.insert();
            }
            purchTableDelete.delete();
            break;
        case Voided::linesVoided :
            while select forupdate purchLineDelete where purchLineDelete.PurchId == purchTableDelete.PurchId
            {
                purchLine = conpeek(purchLineDelete.PurchLine, 1);
                purchLine.insert();
                purchLineDelete.delete();
            }
            purchTableDelete.delete();
            break;
   }
   ttscommit;
}
One can easily create a class using this code to provide an option of restoring the orders. Here is the link to one such project
 
 
 
2 Comments

Posted by on November 13, 2008 in Dynamics AX

 

Insight Into Record IDs

Record IDs are unique IDs. In AX 4.0 Record id is unique for a table, a significant shift in 4.0 when compared to 3.0 where record ids were unique across the application. This allows AX to store more data and support enormous number of records.
 
In AX 4.0 SystemSequences table stores record ids details for all the tables in AX. The generation of Record ids is handled by class SystemSequence.
 
Record ids are generated at the time of saving the record. This is what system does. based on the table id, ID = -1 and name =’SEQNO’ system gets a block of record ids and caches them and stores in the client. The block is of size 250 (in 3.0 we could change the block size, but in 4.0 MS doesnt allow anybody to change the block size).
 
Here is a sample code that shows how we can get next record id in AX 4.0
 
static void getNextRecIdAX40(Args _args)
{
    //Table that stores record ids details for tables
    SystemSequences systemSequences;
 
    //Class that handles Record id generation
    SystemSequence  systemSequence = new SystemSequence();
    ;
 
    select firstonly systemSequences where systemSequences.tabId == tableNum(CustTable);
 
    systemSequence.suspendRecIds(systemSequences.tabId);
    info(strFmt(‘Next record id: %1′, systemSequence.reserveValues(systemSequences.minVal, systemSequences.tabId)));
    systemSequence.removeRecIdSuspension(systemSequences.tabId);
}
 
Here is a sample code that shows how we can get next record id in AX 3.0
 
static void getNaxtRecIdAX30(Args _args)
{
    SystemSequence  systemSequence;
    ;
 
    systemSequence = new SystemSequence();

    systemSequence.flushCache();
    systemSequence.setCacheSize(30)

 

    info(strFmt(‘Buffer size: %1′, systemSequence.getCacheSize()));
    info(strFmt(‘Next record id: %1′, systemSequence.nextVal();));
}
Below is a sample script to generate record ids while you are inserting data from SQL server scripts.
 
[Source for the code: How to Write Data Upgrade Scripts for Microsoft Dynamics AX 4.0 white paper from MS]

CREATE PROCEDURE initFromSMMQuotationTable @DATAAREAID NVARCHAR(3

AS DECLARE @NEXTVAL BIGINT, @ROWCOUNT BIGINT

 

SELECT ……,

RECID = IDENTITY(BIGINT,0,1) AS QUOTATIONID Assign an IDENTITY column with a starting value of 0 incremented by 1

INTO #TEMP

FROM DEL_SMMQUOTATIONTABLE WHERE QUOTATIONSTATUS = 0 SMMQuotationStatus::InProcess

 

SELECT @NEXTVAL=NEXTVAL Retrieve the next value for RECID for this table (by TABID)

FROM SYSTEMSEQUENCES

WITH(UPDLOCK, HOLDLOCK) WHERE ID = -1 AND TABID = 1967

 

INSERT INTO SALESQUOTATIONTABLE

(column-list)

SELECT ……,

RECID = QUOTATIONID+@NEXTVAL When we insert into the permanent table, we add the temporary tables IDENTITY column to the next value retrieved from SYSTEMSEQUENCES

FROM #TEMP

 

SELECT @ROWCOUNT = COUNT(*) FROM #TEMP

 

UPDATE SYSTEMSEQUENCES             We update SYSTEMSEQUENCES to reflect the number of rows that we have added to this table

SET NEXTVAL=NEXTVAL + @ROWCOUNT

WHERE ID = -1 AND TABID = 1967

GO

 
2 Comments

Posted by on September 17, 2008 in Dynamics AX

 

Email techniques in AX 4.0

In this article, I am going to demonstrate different email techniques that can be used in AX 4.0. Following classes can be used to send an email
 
  • Mapi and its related classes
  • SysMailer
  • SysInetMail
  • SysEmailBatch
  • SmmOutlookEmail

MAPI technique:

Following code demonstrates the usage of mapi class for sending email. It uses outlook to send mail.

static void emailThruMapi(Args _args)
{
    MapiEx      mapiEx;
    MapiExMail  mapiExMail;
    boolean     mapiInitialised;
    COM         outlook;
    COM         item;
    COM         outlookNameSpace;
    COM         folder;
    COM         attachments;
    str         storeId;
    str         entryId;

    #define.outlookapplication(‘Outlook.Application’)
    #define.mapi(‘Mapi’)
    #smmMSOutlook2002ObjectModelConstants
    #define.htmlText(‘<html><body>Hi There</body></html>’)
    ;

    outlook             = new COM (#outlookapplication);
    outlookNameSpace    = outlook.getNameSpace(#mapi);
    outlookNameSpace.logon();

    folder = outlookNameSpace.getDefaultFolder(#olFolderInbox);
    item = outlook.createItem(#olMailItem);
    storeId = folder.storeId();

    mapiEx = new MapiEx();

    if(mapiEx && mapiEx.mapiInitialised())
    {
        mapiInitialised = true;
        if (!mapiEx.logon("","",0) || !mapiEx.openMessageStore(storeId))
        {
            mapiInitialised = false;
            mapiEx.logout();
            mapiEx.finalize();
        }

        //To send mail in HTML format
        item.bodyFormat(#olFormatHTML);
        item.htmlBody(#htmlText);

        //To send mail in plain text format
        //item.body(‘Hi There’);

        item.subject(‘Test mail’);

        //—-Attachements——————-
        attachments = item.attachments();
        attachments.add(‘E:\\Test\\4000.pdf’, 1, 1, ’4000.pdf’);

        item.saveSentMessageFolder(outlookNameSpace.getDefaultFolder(#olFolderSentMail));
        item.save();
        entryId = item.entryId();

        mapiExMail = mapiEx.getMailFromEntryId(entryId);
        if (!mapiExMail)
        {
            mapiInitialised = false;
            mapiEx.logout();
            mapiEx.finalize();
        }
    }

    if(item)
    {
        if (mapiInitialised && mapiExMail)
        {
            //TO
            mapiExMail.addRecipient(
‘sumit.loya@sumitloya.com’, "", #olTo);
            //CC
            mapiExMail.addRecipient(
‘sreenath.girigari@sreenath.com’,"",#olCC);
            //BCC
            mapiExMail.addRecipient(
‘ashish.singh@ashishsingh.com’,"",#olBCC);

            try
            {
                mapiExMail.save();
                mapiExMail.close();
                mapiExMail.finalize();
                item = outlookNameSpace.getItemFromID(strupr(entryId));

                //This will display the mail item
                //item.display();

                //This will directly send the mail without poping the mail window
                item.send();
            }
            catch
            {
                if (mapiInitialised)
                {
                    mapiEx.logout();
                    mapiEx.finalize();
                }

                // An error occured sending mail from outlook.
                throw error("@SYS97460");
            }
        }
    }
}

SysMailer:

In the following code you can see how to use SysMailer class for sending mails. To use SysMailer class you need to set Relay server or computer name, user name and password in Administration –> Setup –> Email parameters form. This class internally uses CDO.Message dll for communication purposes. Please note in AX 3.0 SysMailer uses Dundas.Mailer dll for communication.

static void emailThruSysMailer(Args _args)
{
    SysMailer   mailer = new SysMailer();
    SysEmailParameters parameters = SysEmailParameters::find();
    ;

    if (parameters.SMTPRelayServerName)
    {
        mailer.SMTPRelayServer(parameters.SMTPRelayServerName,
                           parameters.SMTPPortNumber,
                           parameters.SMTPUserName,
                           SysEmailParameters::password(),
                           parameters.NTLM);
    }
    else
    {
        mailer.SMTPRelayServer(parameters.SMTPServerIPAddress,
                           parameters.SMTPPortNumber,
                           parameters.SMTPUserName,
                           SysEmailParameters::password(),
                           parameters.NTLM);
    }

    mailer.fromAddress(‘sumit.loya@sumitloya.com’);
    mailer.tos().appendAddress(
‘sumit.loya@sumitloya.com’);
    mailer.body(‘hi’);
    mailer.sendMail();
}

SysInetMail:

SysInetMail internally uses Mapi framework only. But to use SysInetMail one has to setup email templates from Basic –> Setup –> Email templates. SysInetMail will automatically pick sender id, subject, sender name, email text etc. from the email template that you provide while sending from SysInetMail. If you provide a full email address and not the id from Email templates table then also mail will be sent but in that case you need to provide the details yourself.

static void emailThruSysInetMail(Args _args)
{
    SysInetMail mail = new SysInetMail();
    ;

    //To send to an email address directly
    mail.sendMailAttach(
‘sumit.loya@sumitloya.com’, ‘sreenath.girigari@sreenath.com’, ‘Test mail’, ‘Hi There’, false, ‘E:\\Test\\4000.pdf’);
   
    //To use an email template to send mail
    SysInetMail::sendEMail(‘Alerts’, ‘en-us’,
‘sumit.loya@sumitloya.com’);
}

SysEmailBatch:

SysEmailBatch internally uses SysMailer class and is used to send emails in batch. That is this class is batchable. Here is a small example for the class

static void emailThruSysEmailBatch(Args _args)
{
    SysEmailBatch   batch = new SysEmailBatch();
    ;

    batch.parmSenderAddr(‘sumit.loya@solugenix.com’);
    batch.parmEmailAddr(
‘sumit.loya@solugenix.com’);
    batch.parmMessageBody(‘Hi There’);
    batch.parmSubject(‘Test mail’);
    batch.run();
}

SmmOutlookEmail:

This class internally uses Mapi class and is extensively used in HRM module. One feature of this class is that we can specify email groups and it can send mails to all the members defined under this email group. Here is a sample code

static void emailThruSmmOutlookEmail(Args _args)
{
    SmmOutlookEmail smmOutlookEmail = new SmmOutlookEmail();
    ;

    if (smmOutlookEmail.createMailItem())
    {
        smmOutlookEmail.addEMailRecipient(
‘sumit.loya@sumitloya.com’);
        smmOutlookEmail.addSubject(‘Test mail’);
        smmOutlookEmail.isHTML(true);
        smmOutlookEmail.addBodyText(‘<html><body>Hi There</body></html>’);
        smmOutlookEmail.sendEMail(smmSaveCopyOfEmail::No);
    }
}

 

 
4 Comments

Posted by on September 9, 2008 in Dynamics AX

 

All about queries

The query object model contains classes to define and run a query. These objects are used to define the query data source, the fields returned, record ranges and relations to child data sources. The following illustration shows the object model.

 

The query components shown in the previous figure are system classes. The query classes are more visible when you create a dynamic query in code, but they are also used behind the scenes when you create a static query in the AOT.

System class

Description

QueryRun

Executes the query and fetches the data.

Query

The top level of the query definition. This class holds some properties itself and has one or more related data sources.

QueryBuildDataSource

Defines access to a single data source in the query. If more than one data source exists at the same level in a query, they result in separate SQL statements that are executed sequentially. If one data source exists as a child of another data source, a join is created between the two data sources.

QueryBuildFieldList

Defines which fields are returned from the database. The default is that the field list is dynamic, which returns all fields from the data source table, map, or view. Each data source has only one QueryBuildFieldList object, which contains information on all selected fields. It’s possible to specify aggregate functions like SUM, COUNT, and AVG on the field list object.

QueryBuildRange

Defines a subset of records returned based on a single field. A range is translated into a WHERE clause in the query SQL statement. If more than one field is used to limit the query (WHERE clause), the data source will contain more than one range.

QueryBuildDynalink

Contains information regarding a relation (limitation) to an external record. When the query is run, this information is converted to additional entries in the WHERE clause of the query SQL statement. Can only exist on the parent data source of a query. The function is used by forms, when two data sources are synchronized. Then the child data source will contain a dynalink or dynalinks to the parent data source. The function is used even if the two data sources are placed in two different forms but are still synchronized.

QueryBuildLink

Specifies the relation between the two data sources in the join. Can only exist on a child data source.

[Source for above text: Microsoft Dyanmics AX 4.0 Developer Guide]

Below is a job that can illustrate usage different query framework classes

static void queryCreation(Args _args)
{
    Query                   query = new Query(‘MyQuery’);
    QueryBuildDataSource    custTableQBDS, custTransQBDS;
    QueryBuildRange         qbr1, qbr2;
    QueryBuildFieldList     qbFl, qbF2, qbF3;
    QueryBuildLink          qbl;
    QueryBuildDynalink      qbdl;
    QueryRun                qr;

    CustTable               custTable;
    CustTrans               custTrans;
    LedgerTrans             ledgerTrans;
    ;

    custTableQBDS = query.addDataSource(tablenum(custTable));

    //This will enable you to add the fields you like
    //If dynamics is yes all the fields will be automatically added

    custTableQBDS.fields().dynamic(NoYes::No);
    qbFl = custTableQBDS.fields().addField(fieldNum(CustTable, AccountNum));
    qbF2 = custTableQBDS.fields().addField(fieldNum(CustTable, InvoiceAccount));
    qbF3 = custTableQBDS.fields().addField(fieldNum(CustTable, CustGroup));

    //Adding Ranges
    qbr1 = query.dataSourceTable(tableNum(CustTable)).addRange(fieldNum(CustTable, AccountNum));
    qbr2 = query.dataSourceTable(tableNum(CustTable)).addRange(fieldNum(CustTable, InvoiceAccount));
    qbr1.value(SysQuery::value(’4011′));
    qbr2.value(SysQuery::value(’4010′));

    //Add child data source to main datasource
    custTransQBDS = custTableQBDS.addDataSource(tableNum(CustTrans));

    //Two ways to add relation
    //First – This may not work in all the scenarios
    custTransQBDS.relations(true);

    //Second – QueryBuildLink
    qbl = custTransQBDS.addLink(fieldNum(CustTable, AccountNum), fieldNum(CustTrans, AccountNum));

    SetPrefix (‘Queries’);
    setPrefix (‘Dynalinks’);

    qr = new QueryRun(query);
    setPrefix (‘Without’);
    while (qr.next())
    {
        if (qr.changed(tableNum(CustTable)))
        {
            custTable = qr.get(tableNum(CustTable));
        }
        setPrefix (‘Customer – ‘ + custTable.AccountNum);

        if (qr.changed(tableNum(CustTrans)))
        {
            custTrans = qr.get(tableNum(CustTrans));
            info(custTrans.Voucher);
        }
    }

    //Dynalink test so run the loop after dynamically linking with a table
    select firstonly ledgerTrans where ledgerTrans.Voucher == ‘US_800006′ &&
            ledgerTrans.AccountNum == ’130100′;

    custTransQBDS.addDynalink(fieldNum(CustTrans, Voucher), ledgerTrans, fieldNum(LedgerTrans, Voucher));
    custTransQBDS.addDynalink(fieldNum(CustTrans, TransDate), ledgerTrans, fieldNum(LedgerTrans, TransDate));

    qr = new QueryRun(query);

    setPrefix(‘Dynalinks’);
    setPrefix (‘With’);
    while (qr.next())
    {
        if (qr.changed(tableNum(CustTable)))
        {
            custTable = qr.get(tableNum(CustTable));
        }
        setPrefix (‘Customer – ‘ + custTable.AccountNum);

        if (qr.changed(tableNum(CustTrans)))
        {
            custTrans = qr.get(tableNum(CustTrans));
            info(custTrans.Voucher);
        }
    }
}

  

 
Leave a comment

Posted by on September 4, 2008 in Dynamics AX

 

Multi – column selection list

Below is the code that will illustrate creating lists with multiple columns and selection check boxes through code.
 
For this we will use two methods from ‘Global’ class
 
1. selectMultiple (Create the multiple selection list)
2. conView (View the selected rows data)
 
Here is the sample code
 
static void selectMultiple(Args _args)
{
    container con;
    container ret;
    str       label;
    int       id;
    boolean   set;
    boolean   ok;
    ;
    label   = ‘Sumit’+'\n’+'Loya’;
    id      = 1;
    set     = false;
    con     += [sysListSelect::packChoice(label, id, set)];
    label   = ‘Sreenath’+'\n’+'Reddy’;
    id      = 2;
    set     = true;
    con     += [sysListSelect::packChoice(label, id, set)];
   
    label   = ‘Kamalakannan’+'\n’+'Elangovan’;
    id      = 2;
    set     = false;
    con     += [sysListSelect::packChoice(label, id, set)];
    [ok, ret] = selectMultiple(‘Names’, ‘Select one or two names’, con, ['First Name', 'Last Name']);
    conView(ret);
}
Below are the screen shots for the same
 
Multi select list
 
Below is the screenshot of the container view
 
 
 
 
Leave a comment

Posted by on September 4, 2008 in Dynamics AX

 

Creating New Inventory Dimension – Storage

AX has 8 standard inventory dimension here is the classification of these 8 dimensions

Inventory Item Dimensions
  • Size
  • Colour
  • Configuration
Inventory Storage Dimensions
  • Batch
  • Serial
  • Warehouse
  • Location
  • Pallet

Below is the list of objects that needs to be created or modified while creating a new inventory storage dimension. Please note that the list is not exhaustive enough but contains most of the objects that needs to be modified. Let us assume the name of the new dimension would be ‘Test’

Extended Data Types

Following is the table specifying the EDTs that one may need to create for new storage dimension. Reference EDT section specifies which EDT to look at while creating new EDT.

EDT Name

Label

Extends

Type

Reference EDT

InventTestId

Test

SysGroup

String

InventLocationId

InventTestTxt

Text

 

VarString

InventSizeTxt

InterCompanyInventTestId

Test

SysGroup

String

InterCompanyInventLocationId

Tables

  1. New table ‘InventTest’ : Look at the table InventLocation for reference and create a new table InventTest. Do not forget to create the field groups, indexes, properties similar to InventLocation. You can duplicate this table and change the required fields, methods, fields groups, indexes, propeties etc.
  2. Table InventParameters : A new method ‘numRefInventTestId’ needs to be created. Take method ‘numRefInventSerialId’ for an example while creating this method.
  3. Table InventDim : You will have to create a new field ‘InventTestId’. Add this field to field groups, ‘AutoReport’, ‘InventoryDimensions’. Create a new index ‘TestIdx’ with this field, Add this field InvenyTesId to index ‘DimIdx’. Modify the following methods, take cue from code for other dimensions and add the relevant code. Methods are: ‘dim2dimParm’, ‘dimParm2dim’, ‘emptyDimField’, ‘findDim’, ‘findOrCreate’, ‘formQueryAddDynaLink’, ‘isFieldIdItemDim’, ‘isFieldIdTransferReceiptMergable’, ‘isFieldIdTransferTransitReceiptMergable’.
  4. Table InventDimParm : Add a new field ‘InventTestIdFlag’. Add this field to following field groups: ‘AutoReport’, ‘Fixedview’, ‘View’.
  5. Table InventSum : Add a new method named ‘inventTestId’. Take cues from ‘inventSerialId’ method in the table.
  6. Table InventSumDateTrans : You will have to create a new field ‘InventTestId’. Add this field to field groups, ‘AutoReport’, ‘InventoryDimension’. Modify following methods: ‘initFromInventDim’, ‘inventDim’.
  7. Table InventSumDeltaDim : You will have to create two new fields named ‘InventTestId’, ‘InventTestIdFlag’. Add the ‘InventTestId’ field to following groups : ‘AutoLookup’, ‘InventDim’. Add ‘InventTestIdFlag’ field to following groups: ‘InventDimFlags’. Modify following methods: ‘initFromInventDim’, ‘initFromInventDimParm’, ‘initInventDim’, ‘initInventDimParm’.
  8. Table InterCompanyInventDim : You will have to create a new field ‘InventTestId’. Add this field to field groups: ‘AutoReport’, ‘InventoryDimensions’, ‘TmpInventDimensions’. Add the field to following indexes: ‘DimIdx’. Modify following methods: ‘fromInventDim’, ‘toInventDim’, ‘dim2dimParm’, ‘dimParm2dim’, ‘formQueryAddLink’.
  9. Table PBATreeInventDim : You will have to create two new fields named ‘InventTestId’, ‘InventTestIdVar’. Add the ‘InventTestId’ field to following groups : ‘AutoReport’, ‘AllFields’, ‘InventoryDimensions’. Add ‘InventTestIdVar’ field to following field group: ‘InventoryDimensions’. Add a new method named ‘inventTestId’. Take cues from ‘inventSerialId’ method in the table.

Macros

Following is the list of macros that may need to be modified

  1. InventDimSelect
  2. InventDimJoin
  3. InventDimGroupAllFields
  4. InventDimExistsJoin

Classes

  1. New class AxInventTest : Run the class AxGenerateAxBC and select the table InventTest. This will create new AxBC class for InventTest table and you will have to resolve some errors.
  2. Class NumberSeqReference_Inventory : The method ‘loadModule’ has to be modified to create a reference for ‘Test’.
  3. Class InventDimRenameValue : Add a method ‘newInventTest’. Take cue from method ‘newInventSerial’ for definition of this new method.
  4. Class InventDimTracking : Modify method ‘initFromArgs’. Take an example of any of the inventory dimensions for changes.
  5. Class AxInventDim : Add parm and set methods for InventTestId field. Take example of parmInventSerialId and setInventSerialId. Modify method setTableFields to make a call to setInventTestId method.
  6. AxInventDim_PriceDiscTable : Add method setInventTestId. Take example of setInventSerialId for definition of this method.
  7. Class InventDimFixedClass : Add following methods : ‘parmInventTestIdFlag’ – Take example from parmInventSerialIdFlag method and ‘inventTestIdFlag’ – Take example from inventSerialId flag method. Create a macro for InventTestId in ClassDeclaration (look at declarations for other dimensions). Modify method ‘fieldId2Idx’.
  8. Class TradeInterCompany : Modify method ‘createTmpInventSum’.
  9. Class PBALib : Modify method ‘createInventDim’.

Apart from these classes there are many more classes that may need modifications like InventMovement, InventUpd and its child classes. InventOnhand and related classes etc. If you want to know more you can search for keyword ‘InventSerial’ in the classes node and find out.

Forms

Following new forms need to be created

  1. InventTest : Similar to InventSerial.
  2. InvenTestIdLookup : Similar to InventSerialIdLookup

InventTest screen shots

   

Menu Items

Create one display type menu item for InventTest form, similar to InventSerial Menu Item.

Note: There are more objects that can be modified. Also you will have to create a link to open the main form for the dimension at correct place in the correct menu as required.

One more important point that i would like to add if you want to know where all in the application an inventory dimension is having a impact then do a search on entire AOT for Macro #InventDimDevelop. This macro has been included in all the places which are directly impacted by inventory dimensions. Nice way to know the critical objects and take care of them

Below is the link to sample project that contains a sample for new inventory storage dimension.

http://www.axaptapedia.com/Image:SharedProject_InventTest.xpo

Please feel free to aadd any comments and inform me about more objects that needs to be modified

 
3 Comments

Posted by on August 26, 2008 in Dynamics AX

 

Run-Time Form Manipulations

Below is a small code snippet depicting the movement of form controls at run-time. ‘Address’ form is taken as an example to demonstrate this particular feature.
 
Do observe the ‘General’ and ‘Address’ tab pages when you run the job.
 
static void moveControl(Args _args)
{
    FormRun fr = new FormRun(new Args(formstr(Address)));
    Object      parentControl;
    Object      insertAfterControl;
    FormControl control;
    void wait(int _delayTime = 1000)
    {
        Object  waitObject = new Object();
        ;
        waitObject.setTimeOut(‘Notify’, _delayTime);
        waitObject.wait();
    }
    ;
    fr.init();
    fr.run();
    wait(2000);
    parentControl = fr.design().controlName(‘Tab’);
    control = fr.design().controlName(‘TabAddress’);
    insertAfterControl = fr.design().controlName(‘TabOverview’);
    //——-Un comment the line if you want to store the changes in SysLastValue (User setup)
    //parentControl.useUserLayout(true);
    parentControl.moveControl(control.id(), insertAfterControl.id());
    wait(2000);
    fr.close();
}
 
 
 
1 Comment

Posted by on August 26, 2008 in Dynamics AX

 

Convert HTML content to XML

In this write up i will illustrating how we can convert HTML content to XML. Remember one thing that with HTML into picture things will be more or less static. The reason i had to get this code was i had a requirement wherein i had to read a table from a website and store its data into AX table. Below is the code that does the same
 
static void convertHTML2XML(Args _args)
{
    WinInet         wi = new WinInet();
    TextBuffer      tb = new TextBuffer();
    XML             xmlString;
    str             page;
    str             filename;
    int             handle;
    int             i;
    str             tmpHTml;
    container       con;
    str             tmpString;
    int             tillWhere;
    boolean         firstSearch = true;
    int             fields = 0;
    void formXML(int _i, str _value)
    {
       switch(_i)
       {
            case 1, 2: break;
            case 3:  xmlString += ‘<ExchangeRate><Valor>’+_value+’</Valor>’;break;
            case 4:  xmlString += ‘<Ultimor>’+_value+’</Ultimor>’;break;
            case 5:  xmlString += ‘<Var>’+_value+’</Var>’;break;
            case 6:  xmlString += ‘<VarP>’+_value+’</VarP>’;break;
            case 7:  xmlString += ‘<Fecha>’+_value+’</Fecha></ExchangeRate>’;break;
       }
       if (_i == 7)
       {
            fields = 0;
       }
    }
    ;
    xmlString += ‘<?xml version="1.0" encoding="iso-8859-11"?><Root>’;
    handle = wi.internetOpenUrl(‘http://finanzas.inicia.es/cotizaciones/tiposInteres.php?Sesion_BolsamaniaES=2h5opigq5iuhad7te24ip1jpq1′);
    if (handle)
        {
            page = wi.internetReadFile(handle);
            //info(page);
        }
    con = str2con(web::stripHTML(page,true,true),’\n’);
    for (i = 1; i < conlen(con); i++)
    {
            tmpString = strltrim(strreplace(conpeek(con, i),’\t’,”));
            if (firstSearch)
            {
                if (strstartswith(tmpString,’Fecha’))
                {
                    tillWhere = i;
                    firstSearch = false;
                    fields = 1;
                }
           }
           tmpString = strreplace(tmpString,’\t’,”);
           if (strstartsWith(tmpString,’Tipos de interés de los bancos centrales’))
                break;
           if (fields)
           {
                if (tmpString)
                {
                    if (!strstartswith(tmpString,’Fecha’))
                    {
                        formXML(fields, tmpString);
                        fields ++;
                    }
                }
           }
      }
    tb.setText(xmlString + ‘</Root>’);
    tb.toFile(‘E:\\ExchangeRates.xml’); // use xmldocument instead
    wi.internetCloseHandle(handle);
}
Note that I had to hardcode the column names to convert the same to XML. Do write in your comments if you can suggest better alternatives.
 
Leave a comment

Posted by on August 19, 2008 in Dynamics AX

 
 
Follow

Get every new post delivered to your Inbox.

Join 46 other followers