Friday, June 9, 2017

AX 7. Process(update/post opertion) the data after Data Entity import.

It’s a common requirement to perform post actions on records that are imported by Data Entity, for example to invoice created sales order or to post created journal.
To do this we need to add a new method postTargetProcess() to our Data Entity. This method is automatically called by the DMFEntityWriter class in the end of processRecords() method when all records are transferred from staging to target. It is not available from override method drop down on Data Entity because it is static and is called via reflection.

/// 
/// 
/// The definition group that should be processed.
/// 
public static void postTargetProcess(DMFDefinitionGroupExecution _dmfDefinitionGroupExecution)
{
    //check if import job is finished
    if (_dmfDefinitionGroupExecution.StagingStatus == DMFBatchJobStatus::Finished)
    {
        MyStaging myStaging;

        //select all staging records that were processed by current execution job without errors.
        while select myStaging
            where myStaging.DefinitionGroup == _dmfDefinitionGroupExecution.DefinitionGroup
               && myStaging.ExecutionId     == _dmfDefinitionGroupExecution.ExecutionId
               && myStaging.TransferStatus  == DMFTransferStatus::Completed
        {
            //here you can find newly created records and update\post them.
        }
    }
}
Please note that it can be done only in data management scenarios but not via OData because OData updates\inserts records row by row and there is no post event\method to use.

Friday, February 10, 2017

Dynamics 365 - Table Extension Predefined Method() and Userdefined Method():


public static class SalesLine_Extension
{
    [PostHandlerFor(tableStr(SalesLine), tableMethodStr(SalesLine, insert))]
    public static void SalesLine_Post_insert(XppPrePostArgs args)
    {
        SalesLine                  saleslineLoc;
        FormDataSource      salesFormDataSource;           
      
        saleslineLoc        =  args.getthis();

        if(saleslineLoc.SalesId)
        {           
            Info("SalesID existed");
         }
         salesFormDataSource =  FormDataUtil::getFormDataSource(saleslineLoc);
               
         if(salesFormDataSource)
         {
                 salesFormDataSource.research(true);
         }
     }
public static void updateDefaultWarehouse(SalesLine _salesLine)
    {
        InventTable         inventTable;
        ;       
        inventTable   = InventTable::find(_salesLine.ItemId);
    }

[DataEventHandler(tableStr(SalesLine), DataEventType::ModifiedFieldValue)]
    public static void SalesLine_onModifiedFieldValue(Common sender, DataEventArgs e)
    {
        SalesLine       salesLineLocal = sender;
        Table              table1;
       
       
        if(salesLineLocal.SalesId)
       {        
            ttsbegin;
            table1.OrderNumber       = salesLineLocal.SalesId;
            table1.SalesType         = salesLineLocal.SalesType;
            table1.ItemId            = salesLineLocal.ItemId;
            table1.insert();
            ttscommit;
        }
    }
[PostHandlerFor(tableStr(SalesLine), tableMethodStr(SalesLine, update))]
    public static void SalesLine_Post_update(XppPrePostArgs args)
    {
        SalesLine   salesLineLocal;
        SalesTable  salesTable;

        salesLineLocal        =  args.getthis();
        ttsbegin;
            salesTable = salesTable::find(salesLineLocal.SalesId, true);
            if (salesTable)
            {
                salesTable.FieldId= SalesTable_Extension::MethodName(salesTable);
                salesTable.update();
            }
            ttscommit;
        }
[PostHandlerFor(tableStr(SalesLine), tableMethodStr(SalesLine, modifiedField))]
    public static void SalesLine_Post_modifiedField(XppPrePostArgs args)
    {
        FieldId fieldId = args.getArg('_fieldId');
        SalesLine salesLine = args.getThis();

        switch (fieldId)
        {
             case fieldNum(SalesLine, NewItemNumber):
               
               if (salesLine.NewItemNumber)                    
                {
                    ItemId itemId = salesLine.ItemId;
                    Info(strfmt("%1, " ItemId"));
                }
                break;     
        }
    }
[PostHandlerFor(tableStr(SalesLine), tableMethodStr(SalesLine, delete))]
    public static void SalesLine_Post_delete(XppPrePostArgs args)
    {
        SalesLine           salesLine;
        TableName       tableName;       
        Args                _args = new Args();

        salesLine=  args.getthis();

        if(salesLine.RecId)
        {
            ttsbegin;
            delete_from tableName
                where  tableName.OrderNumber    == salesLine.SalesId
                    && tableName.ItemId         == salesLine.ItemId
                    && tableName.LineNum        == salesLine.LineNum;
            ttscommit;
        }
    }

Dynamics 365 - DataSource Method:

public class InventLocation_Extension
{
    ///
    /// Post event handler for InventLocation SalesLine Initialized event.
    ///

    ///
    ///

[FormDataSourceEventHandler(formDataSourceStr(InventLocation, InventLocation), FormDataSourceEventType::Initialized)]
    public static void InventLocation_OnInitialized(FormDataSource sender, FormDataSourceEventArgs e)
    {
        InventLocation  inventLocation = sender.cursor();
        if (inventLocation.InventLocationId)
    {
        info("There is inventLocation Id existed");
    }
    else
    {
        info("There is NO inventLocation Id");
    }
    }
}

Sunday, July 12, 2015

Some string functions used in Axapta:

How to find a string in Ax:

Syntax:

int strFind(
    str _text,
    str _characters,
    int _position,
    int _number)


Ex:
strFind(“abcdef”, “D”, 3, 02) //Returns 4.
  • strFind(“abcdef”, “Bf”, 3, -2) //Returns 2.
  • strFind(“abcdef”, “Z”, 3, 2) //Returns 0.
  • strFind("ABCDEFGHIJ","KHD",1,10); //Returns the value 4 (the position where "D" was found).
  • strFind("ABCDEFGHIJ","KHD",10,-10); //Returns the value 8 (the position where "H" was found).

  • How to search a specific character in String Ax:

    Syntax:

    int strScan(   str _text1, str _text2,    int _position,    int _number)

    Ex:

    //find the current item
        workingItem = Tree.getItem(Tree.getSelection());
        objectId = workingItem.text(); //objectId = "OBJ-0000021: Object Test 1- OBJ"
       
        super();  
        found = strscan(objectId, ":", found, strlen(objectId));
        If (found!=0)
        {
            objectIdActual = subStr(objectId,1,found-1);
            select RecId from objectTable1
            Index ObjectIdx
                where objectTable1.ObjectID == objectIdActual;
            objectBOM.ValidTimeStateUpdateMode (ValidTimeStateUpdate::Correction);
            ttsBegin;
            select forUpdate validTimeState(dateToday) * from objectBOM
                where objectBOM.RecId == element.args().record().RecId; //mroobject.RecId;
            objectBOM.Object = objectTable1.RecId;
            objectBOM.update();
            ttsCommit;
        }

    How to Retrieves part of a string in Ax:

    syntax:
    str subStr(str _text, int _position, int _number)

    Ex:

    subStr("ABCDEFGHIJ",3,5); //Returns the string “CDEFG”.
    subStr("ABCDEFGHIJ",7,-4); //Returns the string “DEFG”.
    subStr("abcdef"),2,99) //Returns the string "cdef".
    subStr("abcdef",2,3) //Returns the string "bcd".
    subStr("abcdef",2,-3); //Returns the string "ab".

    OR

    void test()
    {
    str Name;
    str Result;
    ;
    Name="MED";
    Result=substr(name,1,1);
    info(strfmt("The original Name : %1,the sub string Result : %2",Name,Result));
    }

    Find Starting and ending positions using TextBuffer Class for SubStr():

    int pos;
    TextBuffer textBuffer;

    textBuffer = new TextBuffer();
    textBuffer.setText("ABC DEF GHI JKL MNO ABC ABC");
    pos = 0;

    while (textBuffer.find("ABC",pos))
    {
        print "String found at position: ", textBuffer.matchPos();
        pause;
        pos = textBuffer.matchPos()+1;
    }

    Regular Expressions in Ax:

    http://mybhat.blogspot.in/2012/06/dynamics-ax-regular-expression-match.html





    Tuesday, June 23, 2015

    OutLook integration code in Ax2012 - HR Module->Periodic-> Recruitment->Applicant interviews form.

    OutLook integration code in Ax2012 - HR Module->Periodic-> Recruitment->Applicant interviews form-> Schedule in Microsoft Outlook button.

    Classes-> HCMDiscussion2Outlook :




    Tuesday, March 3, 2015

    Microsoft Dynamics AX 2012 Data Import/Export Framework new version released

    What’s New :  Part of foundation layer in CU7.
    Key Features:

    •150  Entities out of the box ( 80+ new entities added)
    ◦Master Data
    ◦Reference Data
    ◦Journals
    ◦System Configuration data
    ◦Application configuration data – Parameters, Reference data, etc.
    •Support for creating custom entities using Custom entity creation wizard.
    •Import Entity data from multiple sources
    •Flat File – Delimited and Fixed Width
    ◦XML – XML file
    ◦Excel – Excel file
    ◦ODBC – external data bases , Excel files , etc.
    •Export Entity data from AX 2012 to 
    ◦Other AX 2012 environments
    ◦Flat File , XML File and Excel file
    •New Retail specific entities
    •Copy entity data across companies
    •Support for exporting data in multiple formats – delimited, fixed width, XML and excel for bulk data.
    •Enhancements to custom entity creation
    •Compare and Copy Entity data across Legal entities
    •Entity types – Entity, composite entity and flat table.
    •Mapper control
    •Parallel execution support from staging to target using Task Bundling
    •Folder as input for running periodic import  - with  functionality to move files to different folders (In Process, Error and Completed)
    •Error handling  - skipping error rows , etc.
    •Set based support for staging to target
    •Default value Support
    •Number sequence Support
    •External Key mapping Support
    •·         Source to Target in single step
    •·         Multiple AOS support
    •Role based security and privacy extension for Entities and Processing Group
    •Securing Processing group by company.
    .. and lot more.

    Wednesday, February 4, 2015

    Dynamics Ax Internals: Default dimension storage in Ax 2012


    X++ code to retrieve default dimensions (via individual selects)
    Obviously this isn't a particularly efficient approach - it's expanded out like this for the sake of demonstration. In picture-form it may look similar to the following. Note the main tables involved, and the relationships between them:
     
     
    That's a lot of tables! Whereas before we would just reference the elements of the Dimension array, we now have to go through multiple joins to get the same information. The reason for this is the way dimensions are defined and structured in Ax2012. Previously we had a fixed number of dimensions, and a fixed source (the dimension code table), but now we can define an attribute that points to pretty much anything (customers, item groups, warehouses, etc).
    I'll be interested in seeing how this affects reporting that works off direct SQL queries or cubes, as we now have to dynamically link tables based on the underlying source table (identified by DimensionAttribute.BackingEntityType). It could make things a bit tricky, and I suspect we'll have to rely more on generating datasets from within Ax, using the new data provider framework for SSRS.
    So an overview of the main tables involved is:
    Table

    Description
    DimensionAttributeValueSet

    A unique combination of values used for default dimensions. This acts as a
    container for a list of DimensionAttributeValueSetItem records, which link off
    to the specific attribute and attribute value records.
    This is similar in concept to the InventDim table in Ax2009, which stores unique combination of inventory dimension values. It uses a field called Hash, which stores a hash-code for all of the attached values. This is used by Ax when checking whether it needs to create a new entry, or use an existing one. (NB the dimension controllers rely heavily on server-side caching - If you're doing any investigation into the code it may help to disable this via code. Just make sure it's left as-is for production and testing environments).

    DimensionAttributeValueSetItem

    This stores the individual attribute items (I would describe them more as the 'segments'), that make up a value set. This relates to the RecID of the
    DimensionAttributeValueSet via the field of the same name.
    Note that this table doesn't store the actual value. It points to an instance of DimensionAttributeValue (see below), which in-turn links back to the dimension value entitiy (eg Customer table).
    DimensionAttributeValue

    This is a link between an attribute and a value.
    The field EntityInstance points to the RecID of the underlying table/view. NB
    the structure of this is normally that you create a view pointing to the table or tables you want to use for the dimension values. The view can be structured as normal with joins, relations, etc, but will typically only return three fields:
    • Key - RecID of primary table
    • Value - 'Code', such as customer account, item number, etc.
    • Name - The description/name, eg The name on the customer address book entry.
    The convention is that any table used for dimension values is exposed as a view (prefixed with "DimAttribute"). Have a look at the existing DimAttributexxx views in the standard application for plenty of examples.
    DimensionAttribute
    The main attribute table. This will have an entry for 'department', 'cost centre', 'purpose', etc, as well as any other dimensions you define. Each DimensionAttribute points to a 'backing entity' type, which is the table/view id of the underlying data-source.
    For 'custom value' dimensions (ie those that don't point to an existing table), this points indirectly to table DimensionFinancialTag.
    Table overview
    * There's a slight caveat here. If the dimension points to a table like CustTable, how does Ax make sure that there is a corresponding entry in DimensionAttributeValue? The answer is that whenever the dimension value is referenced (for example by selecting it on a form), the system checks whether the entry exists, and if not, it's created. This occurs at:
    Data DictionaryTablesDimensionAttributeValueMethodsinsert

    5
    Data DictionaryTablesDimensionAttributeValueMethodsfindByDimensionAttributeAndEntityInst

    50
    FormsDimensionDefaultingLookupMethodscloseSelect

    17
    And in addition, what if we're referencing the customer dimension, but the underlying customer record is deleted? If you look at CustTable.delete, you'll see a call to DimensionAttributeValue::updateForEntityValueDelete. This goes through any existing references to the corresponding DimensionAttributeValue and clears them. I suspect (at least I'd hope), that if any GL postings have already been made, you won't be able to remove the underlying record.

    Forms

    The class DimensionDefaultingController is used throughout the application to handle the display of default dimensions on master records (customer, suppliers, etc). If you look at the code in the following stack-trace, you'll see query logic similar to the sample at the beginning of this post.

    The DimensionDefaultingController is created on the form, accepting the datasource and field (which in most cases will be DimensionDefault). On the datasource 'active' event, the controller iterates through the relevant dimension value set, and updates the controls. There's a lot more to cover with respect to how dimensions are displayed/updated from the UI - Look out for a future post.
    Dynamics Ax Internals: Default dimension storage in Ax 2012