Showing posts with label InfoLog. Show all posts
Showing posts with label InfoLog. Show all posts

Saturday, November 26, 2022

Get Infolog as a string

/// <summary>
/// Gets infolog content
/// </summary>
/// <returns>infolog string</returns>
public static str getErrorStr(container _cont)
{
	SysInfologEnumerator        enumerator;
	SysInfologMessageStruct     myStruct;
	Exception                   exception;
	str                         error;

	enumerator = SysInfologEnumerator::newData(_cont);

	while (enumerator.moveNext())
	{
		myStruct = new SysInfologMessageStruct(enumerator.currentMessage());
		exception = enumerator.currentException();

		error = strfmt('%1 %2', error, myStruct.message());
	}

	return error;
}

Friday, June 12, 2020

General journals log update bugs

I found a couple questionable things in General journal log updating.

First, Log field limit of 255 characters. It is evidently not enough for relatively long information to be stored for eventual analysis.

Second, the way to update journal logs by copying lines from Infolog afterwards.

Anyway the standard methods meant to shorten the log content do not work properly. Let's see why.





And the method to find a voucher number can speak one language only.





So, we get a poorly built journal log.


It can be fixed by two extensions.

First, let's gather all lines in a set so that they would be present once only.


/// <summary>
/// Extension of LedgerJournalCheckPost to fix a bug
/// </summary>
[ExtensionOf(classStr(LedgerJournalCheckPost))]
final class LedgerJournalCheckPost_Extension
{    

    /// <summary>
    /// Updates the infolog for the given transaction.
    /// </summary>
    /// <param name = "_ledgerJournalTrans">The transaction.</param>
    public void updateTransInfoLog(LedgerJournalTrans _ledgerJournalTrans)
    {
        this.myUpdateTransInfoLog(_ledgerJournalTrans);
        // transLogPoint is already moved forward; therefore the next call will do no change to the log
        next updateTransInfoLog(_ledgerJournalTrans);
    }

    /// <summary>
    /// Updates the infolog for the given transaction.
    /// </summary>
    /// <param name = "_ledgerJournalTrans">The transaction.</param>
    private void myUpdateTransInfoLog(LedgerJournalTrans _ledgerJournalTrans)
    {
        #Define.UserTab('\t')

        Log             logTxt;
        Integer         x = transLogPoint;
        Integer         y = infolog.num(0);
        str             currentLine;
        str             strLine;
        Voucher         voucher;
        List            list;
        ListEnumerator  listEnumerator;
        Set             setAllLines = new Set(Types::String);
        // <GEERU>
        Log             tableLogTxt;
        #ISOCountryRegionCodes
        boolean         countryRegion_RU = SysCountryRegionCode::isLegalEntityInCountryRegion([#isoRU]);
        // </GEERU>

        if (postingResults)
        {
            postingResults.parmLedgerPostingMessageLog(ledgerPostingMessageCollection);
        }

        while (x < y)
        {
            x++;
            // parse all the lines if there are some prefixes
            currentLine     = infolog.text(x);
            list            = strSplit(currentLine, #UserTab);
            listEnumerator  = list.getEnumerator();
            while (listEnumerator.moveNext())
            {
                currentLine = listEnumerator.current();
                if(setAllLines.in(currentLine))
                {
                    continue;
                }
                setAllLines.add(currentLine);
                // <GEERU>
                if (countryRegion_RU)
                {
                    logTxt      =  currentLine + '\r\n';
                    tableLogTxt += logTxt;
                }
                else
                {
                    // </GEERU>
                    logTxt += currentLine + '\r\n';
                    // <GEERU>
                }
                // </GEERU>

                if (logTxt && postingResults != null)
                {
                    if (_ledgerJournalTrans.Voucher == '')
                    {
                        voucher = LedgerJournalCheckPostResults::getVoucherFromLogText(currentLine);
                        if (voucher == '')
                        {
                            // continue because calling the LedgerJournalCheckPostResults.updateErrorLog
                            // method with a blank voucher has terrible performance and will not change the results
                            continue;
                        }
                    }
                    else
                    {
                        voucher = _ledgerJournalTrans.Voucher;
                    }

                    postingResults.updateErrorLog(voucher, logTxt);
                }
            }
        }

        // <GEERU>
        if (countryRegion_RU)
        {
            logTxt = tableLogTxt;
        }
        // </GEERU>

        if (logTxt)
        {
            tableErrorLog += logTxt;
        }

        transLogPoint = y;
     }

}

Second, teach the voucher searching method to speak any language and do not return rubbish instead of a real voucher number.


/// <summary>
/// Extension of LedgerJournalCheckPostResults to fix a bug
/// </summary>
[ExtensionOf(classStr(LedgerJournalCheckPostResults))]
final class LedgerJournalCheckPostResults_Extension
{    
    /// <summary>
    /// See the description for the standard method
    /// </summary>
    /// <param name = "_logText">log text</param>
    /// <returns>Voucher text</returns>
    public static Voucher getVoucherFromLogText(Log _logText)
    {
        // this standard function does not works correctly
        next getVoucherFromLogText(_logText);
        return LedgerJournalCheckPostResults::myGetVoucherFromLogText(_logText);
    }

    /// <summary>
    /// Finds voucher numbers as it should be in all languages
    /// </summary>
    /// <param name = "_logText">Log text</param>
    /// <returns>Voucher text</returns>
    private static Voucher myGetVoucherFromLogText(Log _logText)
    {
        List            list;
        ListEnumerator  listEnumerator;
        Voucher         voucher;
        str             strLine;
        const str       voucherText = '@GeneralLedger:Voucher';
        int             voucherLen  = strLen(voucherText);

        list = strSplit(_logText, ',');

        listEnumerator= list.getEnumerator();
        while (listEnumerator.moveNext())
        {
            strLine = listEnumerator.current();
            if (strContains(strLine, voucherText))
            {
                // delete the word VOUCHER in any language as well as all the spaces around
                voucher = strLRTrim(strDel(strLine, 1, voucherLen));
                break;
            }
        }
        return voucher;
    }

}

Now it looks much better and contains condensed information.


For storing more information, a new memo field can be a solution.

Hope, Microsoft will eventually come up with an optimized approach.

Good logging!


Wednesday, March 6, 2019

Extended version of Universal Field Changer for Microsoft Dynamics AX2012

Oh yeah! The Field changer is still on the road! Now equipped with two updating options:


Feel free to use this surgeon's tool at your own risk! Grab it from here.

Wednesday, March 2, 2016

Infolog in batch task history using SysOperation framework

If you add tasks to a batch job using SysOperation framework, like the following:


[...]

while (mapIterator.more())
    {
        if (!batchHeader)
        {
            batchHeader = this.getCurrentBatchHeader();
        }

        controller = wblDirPartyMergeTaskController::construct();

        [...]

        if (batchHeader)
        {
            batchInfo = controller.batchInfo();
            batchHeader.addRuntimeTask(controller, this.getCurrentBatchTask().RecId);
        }
        else
        {
            setPrefix(batchInfoStr);
            controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);
            controller.run();
        }
      
        mapIterator.next();
    }

    if (batchHeader)
    {
        batchHeader.save();
    }
}

You need to set Execution mode to Synchronous while initializing your service class controller.

// class wblDirPartyMergeTaskController extends SysOperationServiceController
public void new()
{
    super();
    // default for controllers in these classes is synchronous execution
    // batch execution will be explicitly specified. The default for
    // SysOperationServiceController is ReliableAsynchronous execution
    this.parmExecutionMode(SysOperationExecutionMode::Synchronous);

    this.parmOther(newValue);
}
      
Otherwise you will see nothing in bath tasks history infolog.

Wednesday, February 8, 2012

How to go to the object from Infolog

Usually, it is more useful if the user can go directly to the object from an Infolog line.

For example, we need to see all the BOM without any active version. By the way, this is a good example, too, for what we cannot do by means of Advanced Filtre but quickly by coding a short job.


static void SIBOMwithNoActiveVersion(Args _args)
{
    BOMTable    bomTable;
    BOMVersion  bomVersion;
    int         nmbr, i;
    boolean     printOnly = false;
    ;

    SetPrefix('BOMs without any active version');
    Nmbr = 0;

    while select bomTable
    notexists join bomVersion
    where bomVersion.Active == NoYes::Yes
    && bomVersion.BOMId == bomTable.BOMId
    {
        nmbr++;
        info(strfmt("%1 %2, %3, %4, %5, %6, %7",
                                          nmbr,
                                          bomTable.BOMId,
                                          bomTable.Name,
                                          bomVersion.ItemId,
                                          bomVersion.Name,
                                          bomVersion.Active
                                          ),"",
        SysInfoAction_TableField::newBufferField(bomTable, fieldnum(bomTable, BOMId)));

    }

}

Now the user can opt for Show from the context menu or just double-click on the interesting line to open the related form.





Tuesday, January 17, 2012

Universal Field Changer new version for Microsoft Dynamics AX2012

So, I took my old project from the case just to add a new feature: this time I would like to get all the table fields with their labels in the user's language.



Frankly seaking I was going to get it in a grid in order to export to Excel; but unfortunately I did not find a fast way to show my temporary table. Finally, I just use InfoLog in the comma separated format that can be used lately to open in Excel.



Enjoy, anyway!

Description:

Universal Field Changer class for Microsoft Dynamics AX2012:

- collects all the fields from all the tables in AOT in temporary tables;
- makes possible to change any values using filtres by table and field names and existing values;
- provides access to SQL query string;
- prints the field lists with labels in user's language;
- creates dynamically all the form controls and uses method overloading and can be used as a tutorial;

Thursday, April 17, 2008

SysInfoAction and InfoLog

To do an action from InfoLog dialog you need to create SysInfoAction using one of SysInfoAction_xx classes or create your own descendant class.

For example, to allow the current user open E-mail parameters form by appropriate menu item:

Info('Check e-mail parameters,'',SysInfoAction_MenuFunction::newMenuItem('SysEmailParameters',MenuItemType::Display));