X++

AX 2012: General journal posting in X++

Posted on Updated on

Purpose:

The purpose of this document is to describe how we can quickly post general journals (also known as GL opening balances or simply GL balances) across all the companies in X++.

This particularly comes in handy when GL balances are loaded in bulk through DIXF and the customer wants to post the loaded journals automatically as part of the DIXF load process.

Business requirement:

Ability to post GL balances across all the companies in X++ along with the posting log.

Prerequisites:

Fiscal periods are open for the relevant periods.

Assumptions:

The number sequence for the Journal batch number contains a constant segment “_GL” to designate it as a GL balance entry. Based on this assumption the given code filters GL balance record from the LedgerJournalTable.

Development:

1. Create a posting log table MAKLedgerJournalPostLog with the following fields:

untitled

where,

JournalNum        – uses LedgerJournalId EDT
Posted                  – uses NoYesId EDT
PostingLog          – uses Log EDT
TransactionTime – uses DateTimeExecuted EDT

2. Create an AOT job with the following code:

// Developed on 28 Dec 2015 by Muhammad Anas Khan
// Blog: dynamicsaxinsight.wordpress.com
// LinkedIn: pk.linkedin.com/in/muhammadanaskhan
// Description: Ability to confirm purchase order
static void MAKLedgerJournalPost(Args _args)
{
    LedgerJournalTable      ledgerJournalTable;
    LedgerJournalName       ledgerJournalName;
    LedgerJournalCheckPost  ledgerJournalValiate, ledgerJournalPost;
    Log                     errorMessage;
    SysInfologEnumerator    sysInfologEnumerator;
    MAKLedgerJournalPostLog postingLogTable;

    //Private method
    void insertLog(log _log, ledgerjournalid _journalNum, NoYes _post)
    {
        postingLogTable.clear();
        postingLogTable.PostingLog = _log;
        postingLogTable.JournalNum = _journalNum;
        postingLogTable.TransactionTime = DateTimeUtil::utcNow();
        postingLogTable.Posted = _post;
        postingLogTable.insert();
        infolog.clear();
    }

    delete_from postingLogTable;

    while select crossCompany * from ledgerJournalTable
        where ledgerJournalTable.JournalNum like '*_GL*'
            && ledgerJournalTable.Posted == NoYes::No
    {
        try
        {
            changeCompany(ledgerJournalTable.dataAreaId)
            {
                ledgerJournalName = LedgerJournalName::find(ledgerJournalTable.JournalName);
                ledgerJournalValiate = ledgerJournalCheckPost::newLedgerJournalTable(
                    ledgerJournalTable,
                    NoYes::No);
                
                ledgerJournalValiate.run();

                if (!ledgerJournalValiate.tableErrorLog())
                {
                    ledgerJournalPost = ledgerJournalCheckPost::newLedgerJournalTable(
                        ledgerJournalTable,
                        NoYes::Yes);
                    
                    ledgerJournalPost.run();
                    
                    insertLog(
                        ledgerJournalValiate.tableErrorLog(),
                        ledgerJournalTable.JournalNum,
                        NoYes::Yes);
                }
                else
                {
                    insertLog(
                        ledgerJournalValiate.tableErrorLog(),
                        ledgerJournalTable.JournalNum,
                        NoYes::No);
                }
            }
        }
        catch(Exception::Error)
        {
            sysInfologEnumerator = SysInfologEnumerator::newData(infolog.infologData());
            errorMessage = "";

            while (sysInfologEnumerator.moveNext())
            {
                errorMessage += sysInfologEnumerator.currentMessage() + "; ";
            }

            insertLog(
                errorMessage,
                ledgerJournalTable.JournalNum,
                NoYes::No);
        }
    }

    info("Posting completed. Please check log for posting results.");
}

3. After running the above AOT job, you can find the posting log by querying records in table MAKLedgerJournalPostLog from SQL server client.

AX 2012: Sales Order Posting Custom Validations

Posted on Updated on

It is often a customer requirement to check for custom validations before posting a Sales Order. The best place to put your code for custom validations can be found below:

Validations for Sales Table:

AOT > Classes > SalesFormLetterProvider > checkHeading()

For example,

To check for custom field SalesTable.MAKBlocked, this is how custom validation should be added in checkHeading method. You may refer to Line# to know exactly where I have placed the code.

Untitled

Validations for Sales Line:

AOT > Classes > SalesFormLetterProvider > checkLines()

For example,

To check for custom field SalesLine.MAKBlocked, this is how custom validation should be added in checkLines method. You may refer to Line# to know exactly where I have placed the code.

Untitled

AX 2012: Using Temporary Table as Form’s Datasource

Posted on Updated on

First add a method on the form to populate records in the temporary table:

public LabelsTable populateRecords()
{
    SysDictTable    dictTable = new SysDictTable(tableNum(PurchLine));
    SysDictField    dictField;
    TreeNode        treeNode;
    LabelsTable     labelsTableLocal;                 // Temporary table (InMemory)
    FieldId         fieldId = dictTable.fieldNext(0);

    while (fieldId)
    {
        dictField = dictTable.fieldObject(fieldId);

        if (dictField.isSql() && !dictField.isSystem() && dictField.name() != "Modified")
        {
            treeNode = dictField.treeNode();
            labelsTableLocal.Field = dictField.name();
            labelsTableLocal.Label = treeNode.AOTgetProperty("Label");
            labelsTableLocal.insert();
        }

        fieldId = dictTable.fieldNext(fieldId);
    }
    
    return labelsTableLocal;
}

 

Then override form’s init() method and add the following code to display the temporary table data on the form:

public void init()
{
    super();
    
    LabelsTable.setTmpData(element.populateRecords());
}

AX 2012: Loop through all the fields of a table in X++

Posted on

This is how we can loop through all the fields of a table in X++:

static void Job1(Args _args)
{
    SysDictTable    dictTable = new SysDictTable(tableNum(PurchLine));
    SysDictField    dictField;
    TreeNode        treeNode;
    FieldId         fieldId = dictTable.fieldNext(0);

    while (fieldId)
    {
        dictField = dictTable.fieldObject(fieldId);

        if (dictField.isSql() && !dictField.isSystem())
        {
            treeNode = dictField.treeNode();
            info(strFmt("%1 | %2 | %3",
                dictField.name(),                                               // Field name
                treeNode.AOTgetProperty("Label"),                               // Label id
                SysLabel::labelId2String(treeNode.AOTgetProperty("Label"))));   // Label string
        }

        fieldId = dictTable.fieldNext(fieldId);
    }
}

AX 2012: evalBuf – Evaluate string expression in X++

Posted on Updated on

evalBuf Function is a very strong API in X++. We can quite easily evaluate complex algebraic expressions given in string and the result is also given back in string. This API should be used along with CodeAccessPermission. The downside of using this API is that the code using this function is not supported in CIL.

This is how we can use this API

static void JobEvalBufDemo(Args _args)
{
    ExecutePermission perm;
    str strCodeToExecute = "2 + (5*10)";
    str strResult;
    ;

    perm = new ExecutePermission();

    if (perm != null)
    {
        perm.assert();				// Grants permission
        strResult = EvalBuf(strCodeToExecute);
        CodeAccessPermission::revertAssert();	// Revoke permission
    }

    info(strFmt("Result is: %1", strResult));
    // info: "Result is: 52"
}

AX 2012: Add dynalink in X++

Posted on Updated on

Use the following code to add dynalink to the form datasource query:

public void init()
{
    super();
    
    this.query().dataSourceTable(tableNum(MzkPurchTrackingDetailsTable)).clearDynalinks();
    this.query().dataSourceTable(tableNum(MzkPurchTrackingDetailsTable)).addDynalink(
        fieldNum(MzkPurchTrackingDetailsTable, PurchId),
        PurchParmTable,
        fieldNum(PurchParmTable, PurchId));
}

Where,

  • First parameter is the source table field
  • Second parameter is the destination table
  • Third parameter is the destination table field

AX 2012: Refresh caller form datasource

Posted on Updated on

The following code refreshes the caller form datasource from the calling form:

void clicked()
{
    Args    arg = new Args();
    FormRun formRun;    
    ;

    arg = new args(formstr(YourForm));
    arg.record(yourTable);
    arg.caller(this);
    formRun = classFactory.formRunClass(arg);
    formRun.init();
    formRun.run();
    formRun.wait();
    formRun.detach();
    
    YourTable_DS.reread();
    YourTable_DS.rereadReferenceDataSources();
    YourTable_DS.research(true);
}

1. reread() – Rereads the current record from the database
2. rereadReferenceDataSources() – Rereads the reference records of the current record from the database
3. research(true) – true parameter retains the current position of the dataSource cursor after researching