Wednesday 6 January 2021

Create ledger dimension for LedgerJournalTrans, to use in LedgerDimension and OffsetLedgerDimension fields

This simple code below may be used, when you want to get a RecId for LedgedDimension of OffsetLedgerDimension field in LedgerJournalTrans table.
It is working in Dynamics AX 2012 and in Dynamics 365, but it is good to know that in Dynamics 365 it is rather obsolete, and it is advised to create ledger dimension value in different way.


container ledgerDimensionContainer;


RefRecId ledgerDimension;


//First two items in contaner is MainAccountId, in that example "430-01"

//Third element is number of dimensions which you want to use. In that example is '3'

//As fourth, sixth, eights, ...., you have to put dimension name. In that example are "Department", "CostCenter", "Project"

//As fifth, seventh, nineth, ...., you have to put dimension's values, in that example are "Warsaw", "IT", "Scrum"

ledgerDimensionContainer = ['430-01', '430-01', 3, 'Department', 'Warsaw', 'CostCenter', 'IT', 'Project', 'Scrum'];


ledgerDimension = AxdDimensionUtil::getLedgerAccountId(ledgerDimensionContainer);


//Here in ledgerDimension variable is LedgerDimension ready to use in LedgerJournalTrans table

return ledgerDimension;

Wednesday 6 May 2020

Data dictionary icon is red. Needed to be synchronize

Sometimes, when you're creating a new Extended Data Type, system is asking you about synchronization.
You don't want to synchronize all the time, when new EDT has been created, because you know, that you have to create more EDTs in that project.

But, when you will not synchronize, an icon of Data dictionary turns red.



It means that system knows that it is needed to be synchronized, and it can start long synchronization process when you don't want to do that.

To avoid such unnecessary synchronization, you can just use that job

static void Job23(Args _args)
{
    new SqlSyncPending().databaseTouched(false);
}



Just remember to synchronize system, after you've created all needed EDTs.

Tuesday 17 December 2019

Date to DateTime conversion and comparison

If you want just to convert specified date to dateTime, and it should be beginning of this day (date time), you can use

DateTimeUtil::newDateTime(mkDate(17, 12, 2019), 0);
//17th of December 2019

Or just
DateTimeUtil::newDateTime(systemdateGet(), 0);
//current system date

But if you want to have maximum time from that date, so it means hour 23:59:39
DateTimeUtil::newDateTime(mkDate(17, 12, 2019), 86400);

As 86400 is maximum time value for given date (24 hours * 60 minutes * 60 seconds)

In Dynamics 365FO, systemDateGet() is usable but obsolete, and you'll get Best Practice warning.
You should rather use
DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone())



Wednesday 11 December 2019

How to change Created by on batch job. Change user on which batch job will be executed

It's not good idea to create batch jobs using standard user account, because such user may be disabled after some time, and there will be nobody with knowledge, why do some batches (even very old) just  not work.

But, if we have plenty of such batches, and we want them to be executed on some service account or on admin account, we don't have to recreate them.

There are two tables which keep information which user will execute those batches:
- Batch
- BatchJob

You have to change fields ExecutedBy and CreatedBy in Batch table, and only CreatedBy in BatchJob table

You can do that manually from Microsoft SQL Server Management Studio, or just writing a script

begin transaction

update BATCHJOB
set CREATEDBY='SomeServiceAccount'
where CREATEDBY='admin_10'

update BATCH
set EXECUTEDBY='SomeServiceAccount', CREATEDBY='SomeServiceAccount'
where CREATEDBY='admin_10'

commit transaction

Thursday 5 December 2019

How to build complex query range in query

Sometimes there is a need to query some records using OR and put it to a query range
For example: we need to query records which are from the past and from the future.

Below, you can find an example of such query on table called just Table1
There is no date effective property on the table, so fields ValidFrom and ValidTo are just Date type fields created on that table.
As you can see all range has been built on ValidFrom field, even when it touches also ValidTo.


static void Job21(Args _args)
{
    Query                   query = New Query();    
    QueryRun                queryRun;
    QueryBuildDataSource    qbds;
    str                     range;
    Table1                  table1;
    
    qbds = query.addDataSource(tableNum(Table1)); 
      
    qbds.addRange(fieldNum(Table1, ValidFrom));
    
    range = strFmt('((%1.%2 < %3) && (%4.%5 < %6)) || ((%7.%8 > %9) && (%10.%11 > %12))',
        qbds.name(),
        fieldStr(table1, ValidFrom),
        date2StrXpp(today()),
        qbds.name(),
        fieldStr(table1, ValidTo),
        date2StrXpp(today()),
        /// after or
        qbds.name(),
        fieldStr(table1, ValidFrom),
        date2StrXpp(today()),
        qbds.name(),
        fieldStr(table1, ValidTo),
        date2StrXpp(today()));    
    
    qbds.addRange(fieldNum(Table1, ValidFrom)).value(range);    
}

Tuesday 3 December 2019

Progress bar

How to create a progress bar from X++



static void Job39(Args _args)
{
    SalesLine               salesLine;
    Counter                 i;
    SysOperationProgress    progressBar = new SysOperationProgress();    
    #avifiles

    select count(RecId) from salesLine;
    
    progressBar.setCaption('Searching...');
    progressBar.setAnimation(#AviUpdate);    
    progressBar.setTotal(salesLine.recId);    
    
    while select salesLine
        where !salesLine.RemainInventFinancial
            && !salesLine.RemainSalesPhysical
    {
        i++;
        
        progressBar.setText(strfmt('Row %1', i));
        progressBar.setCount(i, 1);        
    }

}

Thursday 14 November 2019

Get file name from file path by X++

When you want to get a file name from long file path, you can use a method fileNameSplit() which you may find in Global class.

This method returns a container, where the first value is a path, the second file name (without extension), and the last third one is an extension.

You have to remember that path is returned already with last backslash '\', so for example C:\Test\
And also what is important, that last value (extension) is returned with dot '.', for example '.xml'

An example of code below

static void SplitFileName(Args _args)
{
    Filename    fileNameOrig = @'\\NetworkPath\In\FileName.xml';
    fileName    filePath;
    Filename    fileName;
    fileName    fileExt;
    
    [FilePath, fileName, fileExt] = fileNameSplit(fileNameOrig);

    info(FilePath);
    info(Filename);
    info(fileExt);

}

And a result