2012年10月29日星期一

How to achieved to copy data from origanl table instance to new table instance


One of the useful method from standard AX is the buf2Buf(), it behave similar to table's .data() method with one difference - buf2buf doesn't copy system field. Another reason of using buf2buf is copying of record from one company to another company. When using changeCompany(), the .data() method copy all field including DataAreaId and when insert(), DataAreaId field does not change, hence, the record is not inserted into the company indicated in the changeCompany(), here is where buf2Buf() comes in handy - copy all field except system field, then during insert(), let the system assign values to system field.

Sometimes, there're some functionality requires copy data from one table to another table which has similar structure (Eg. to history or logging table), in this case, the .data() and buf2Buf() cannot be used. But we can make some modification to the buf2Buf() method to copy based on field name instead of field Id.

Below are the two methods:
global::buf2Buf()  - Standard AX method
THK_buf2BufBySameFileldName()  - modified method

//Standard AX method, copy data based on field Id

1)global::buf2Buf

static void buf2Buf(
    Common  _from,
    Common  _to
    )
{
    DictTable   dictTable = new DictTable(_from.TableId);
    fieldId     fieldId   = dictTable.fieldNext(0);

    while (fieldId && ! isSysId(fieldId))
    {
        _to.(fieldId)   = _from.(fieldId);
        fieldId         = dictTable.fieldNext(fieldId);
    }
}


2)THK_buf2BufBySameFileldName
//Modified method, copy data from one table to another table with similar structure
// USR Changed on 29 十月 2012 at 11:35:14 by admin

static void THK_buf2BufBySameFileldName(Common  _from, Common  _to)
{
    DictTable   dictTableFrom   = new DictTable(_from.TableId);
    DictTable   dictTableTo     = new DictTable(_to.TableId);
    DictField   dictFieldFrom;
    FieldId     fieldIdFrom     = dictTableFrom.fieldNext(0);
    FieldId     fieldIdTo;
;
    while (fieldIdFrom && ! isSysId(fieldIdFrom))
    {
        dictFieldFrom   = new DictField(_from.TableId, fieldIdFrom);
        if(dictFieldFrom)
        {
            fieldIdTo = dictTableTo.fieldName2Id(dictFieldFrom.name());
            if(fieldIdTo)
                _to.(fieldIdTo) = _from.(fieldIdFrom);
        }
        fieldIdFrom = dictTableFrom.fieldNext(fieldIdFrom);
    }
}

2012年10月28日星期日

How to achieve save(put) and get Last Value on Form


1) Use pack/unpack on Form

If you have used runbase classes, you may be impressed by its pack and unpack mechanism. It allows users to keep the input values of last time. If you want to implement the same mechanism on the Form, it is easy as well:

Besides Pack/Unpack, add these six methods:


public class FormRun extends ObjectRun
{
    ...
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        values...
    #endmacro
}

public container pack()
{
    return [#CurrentVersion, #CurrentList];
}

public boolean unpack(container packedClass)
{
    Integer version = conpeek(packedClass,1);

    switch (version)
    {
        case #CurrentVersion:
            [version,#CurrentList] = packedClass;
            break;
        default:
            return false;
    }
    return true;
}


public dataAreaId lastValueDataAreaId()
{
return curExt();
}

private UserId lastValueUserId()
{
return curuserid();
}
private UtilElementType lastValueType()
{
return UtilElementType::Form;
}
private IdentifierName lastValueElementName()
{
return this.name();
}
private IdentifierName lastValueDesignName()
{
return ;
}

//this is called when no last value is retrieved
void initParmDefault()
{
}

further, in Close method of the form:
public void close()
{
super();
//add saveLast method after super()
xSysLastValue::saveLast(this);
}

in init method of the form:
public void init()
{
;
    //Add getLast method before super()
xSysLastValue::getLast(this);
super();

}
2)Container lastValues;
get
 lastValues  = xSyslastValue::getValue(curext(), curUserId(), utilElementType::Form,element.name() + "out","");
put
xSyslastValue::putValue([JournalName],curext(), curUserId(), utilElementType::Form,element.name(),"");

How to using X++ code create and posting a ledger journal


Here is the adapted example for posting a ledger journal line. Simply replace values between #s (#value#):

static void ExampleLedgerJournal(Args _args)
{
    LedgerJournalTable      ledgerJournalTable;
    LedgerJournalTrans      ledgerJournalTrans;
    LedgerJournalCheckPost  ledgerJournalCheckPost;
    NumberSeq               numberSeq;
    ;

    ttsbegin;

    // Journal name
    ledgerJournalTable.JournalName = "#JOURNALNAME#"; // ex. Daily, daytrans, etc.
    ledgerJournalTable.initFromLedgerJournalName();
    ledgerJournalTable.Name = "#DESCRIPTION#";  // description for this journal
    ledgerJournalTable.insert();

    // Voucher
    numberSeq = NumberSeq::newGetVoucherFromCode(LedgerJournalName::find(ledgerJournalTable.JournalName).VoucherSeries);
    ledgerJournalTrans.Voucher = numberSeq.voucher();

    // Lines
    ledgerJournalTrans.JournalNum = ledgerJournalTable.JournalNum;
    ledgerJournalTrans.CurrencyCode = CompanyInfo::standardCurrency();
    ledgerJournalTrans.ExchRate = Currency::exchRate(ledgerJournalTrans.CurrencyCode);
    ledgerJournalTrans.AccountNum = "#ACCOUNT#";
    ledgerJournalTrans.AccountType = LedgerJournalACType::Ledger;
    ledgerJournalTrans.AmountCurDebit = #VALUE#;
    ledgerJournalTrans.TransDate = systemDateGet(); //Avoid the Today function
    ledgerJournalTrans.OffsetAccount = "#OFFSET ACCOUNT#";
    ledgerJournalTrans.Txt = "#TXT#";
    ledgerJournalTrans.insert();

    //Posting the Journal
    ledgerJournalCheckPost = LedgerJournalCheckPost::newLedgerJournalTable(ledgerJournalTable, NoYes::Yes);
    ledgerJournalCheckPost.run();
    
    ttscommit;
}

How to using X++ code to delete AOT nodes

1) TreeNode class
Sometimes, bad objects can not be removed from the AOT because when you click them, Axapta crush !! Look up this job:
static void DeleteDamnTables(Args _args)
{
    TreeNode edt, edt2;
    ;

    edt = TreeNode::findNode("Data Dictionary\\Tables");
    if (edt != null)
    {
        edt2 = edt.AOTfindChild("tablename");
        if (edt2 != null)
            edt2.AOTdelete();
    }
}
2) UtilIdElements solution
Warning: Serious problems might occur if you modify the UtilIdElements table using this or another method. Modify system tables at your own risk.

The code:

static void Job1(Args _args) 
{ 
    UtilIdElements utilElement; 
    ; 

    ttsbegin; 

    select utilElement 
        where utilElement.name == 'myElementName'
        && utilElement.utilLevel == utilEntryLevel::cus // any layer 
        && utilElement.recordType == utilElementType::Table; // object type 

    if (utilelement) 
    { 
        utilElement.delete(); 

        ttscommit; 
        info('Record should be deleted now.'); 
    } 
    else 
    { 
        ttsAbort; 
        info('Could not delete record, or it was not found.'); 
    } 
}

How to invoking to the caller Form/DataSource method


If we need to notify events to the caller form we can try this pattern:

In the child form, make a method named updateCaller and invoke it when you want to notify the parent:

void updateCaller()
{
    Common common;
    Object dataSource;
    Object caller;
    ;

    //-----------------------------------
    //We are notifying using the dataSource
    common = element.args().record();
    if (common
        && common.isFormDataSource()
        && formDataSourceHasMethod(common.dataSource(), identifierstr(SomethingWasHappend)))
    {
        dataSource = common.dataSource();
        dataSource.SomethingWasHappend();
    }
    //-----------------------------------

    //-----------------------------------
    //We are notifying using the form
    caller = element.args().caller();
    if (caller
        && classidget(caller) == classnum(SysSetupFormRun)
        && formHasMethod(caller, identifierstr(SomethingWasHappend)))
    {
        caller.SomethingWasHappend();
    }
    //-----------------------------------
}

Implement the handling method in the parent form:

void SomethingWasHappend()
{
    ;
    info("Something was happend (Form)");
}

Or if you prefer, in the DataSource:

void SomethingWasHappend()
{
    ;
    info("Something was happend (DataSource)");
}

Invoking it from a simple button inside the child form:

void clicked()
{
    super();

    element.updateCaller();
}


See a pratical sample in the form MarkupTrans (DataSource -> method write).

2012年10月19日星期五

How to using SQL statement directly execute to Dynamics AX


This is not a recommended approach to run query against AX, but sometimes there's a need for it (Eg. stored procedure), below is the sample code to run SQL statement directly to the AX database (you'll need to do manual security check due to this approach will skip AX security validation).

I usually has a class with 2 simple method:
  • executeUpdate
    Used to execute INSERT, UPDATE, DELETE statement
  • executeQuery
    Used to execute SELECT statement

//Class :: SQLUtility ==========================================
class SQLUtility
{
}

//Method :: executeUpdate ======================================
server static int executeUpdate(str sql, Connection connection = new Connection())
{
    SqlStatementExecutePermission   sqlPerm;
    Statement                       statement;
    int                             rowsAffected;
    ;

    if(sql)
    {
        sqlPerm = new SqlStatementExecutePermission(sql);
        sqlPerm.assert();

        statement    = connection.createStatement();

        rowsAffected = statement.executeUpdate(sql);

        CodeAccessPermission::revertAssert();
    }

    return rowsAffected;
}

//Method :: executeQuery =======================================
server static ResultSet executeQuery(str sql, Connection connection = new Connection())
{
    SqlStatementExecutePermission   sqlPerm;
    Statement                       statement;
    ResultSet                       resultSet;
    ;

    sqlPerm = new SqlStatementExecutePermission(sql);
    sqlPerm.assert();

    statement = connection.createStatement();
    resultSet = statement.executeQuery(sql);

    CodeAccessPermission::revertAssert();

    return resultSet;
}

//Job :: Job_DirectSQLTest =====================================
static void Job_DirectSQLTest(Args _args)
{
    ResultSet       testRS;
    ;
    
    testRS = SQLUtility::executeQuery("select top 10 ItemId from InventTable");
    while(testRS.next())
    {
        info(testRS.getString(1));
    }
}

Result of running the job " Job_DirectSQLTest  "

2012年10月12日星期五

A table, Extended Data Type, Base Enum or class called THK_AdjJournalTrans already exists. Import of Table aborted.


Issue :
I have created one new module and taken xpo of it from one server and when i going to import that xpo to another server, the following error is coming and import has been aborted. How to solution this case?


Error info:
"A table, Extended Data Type, Base Enum or class called THK_AdjJournalTrans already exists. Import of Table aborted. "
Note: I have already searched this name object (THK_AdjJournalTrans ) in AOT, which is not available in AOT before import.

Resolved :
Maybe the thing is that IMPORT should be done wihout IDs.So I try delete the cache files for my client AX system environment.
Cache files - close the client and then delete files .AUC files in cache directory:
* C:\Documents and Settings\[USERNAME]\Local Settings\Application Data for Win Xp or Server 2003.
* C:\Users\[USERNAME]\AppData\Local for Vista/Win7.   [such as : ax_{19F9525D-A1C5-4858-97D9-7863F8FE814A}.auc]