Saturday, August 27, 2016

Job to calculate batch duration

In support role, batch duration value is used when we need to plan batch schedule. I will show the simple code to find it.

Below picture tell us, the average duration of this batch is 14.02 seconds.







Simple code is here.

Before run it, check these points:

  • Name of job(Batch)
  • Output file location
  • Capturing time (From.. To..)


Have a nice day! :)

static void checkTimeDiff(Args _args)
{
    // ------------- Declaration -------------
    BatchJob            batchJob;
    BatchJobHistory     batchJobHistory,
                        batchJobHistoryNonDaily;
    BatchCaption        jobName;

    int                 i=0,
                        iMax=0,
                        posOfDot,
                        numPerJob;
    int64               numOfSecs;
    Real                sumPerJob,
                        avgValue;

    container           listOfJobName   = ['aaaaa', 
                                           'bbbbb'
                                           ],
                        recurrenceData;   

    date                parmBeginDate,
                        parmEndDate;
    utcDateTime         utcStartDate,
                        utcEndDate,
                        utcParmBeginDate,
                        utcParmEndDate;
    TimeOfDay           recurTime;
    str                 strCaption,
                        strDescription,
                        strRecurPeriod,
                        strRecurTime,
                        strCurStartEnd,
                        strStart,
                        strEnd,
                        strPerJob,
                        strBatchStatus;

    // Declaration: CSV file
    CommaTextIo file;
    container           line,
                        lineHeader      = ['Job Name', 'Start time', 'End time', 'Diff', 'Status'];    
    CustTable custTable;
    #define.filename(@'C:\Test\checkTimeDiff.csv')
    #File
    ;


    // ------------- Process -------------

    // Check file availability
    file = new CommaTextIo(#filename, #io_write);
    if (!file || file.status() != IO_Status::Ok)
    {
       throw error("File cannot be opened.");
    }

    
    parmBeginDate   = 01\07\2016;    // it's 01-Jul-2016
    parmEndDate     = 31\07\2016;    // it's 31-Jul-2016    


    utcParmBeginDate  = DateTimeUtil::newDateTime(parmBeginDate,0);
    utcParmEndDate    = DateTimeUtil::newDateTime(parmEndDate,86400);    
    

    // Write header
    line = lineHeader;
    file.writeExp(line);

    // Write line
    iMax = conLen(listOfJobName);
    while (i < iMax)
    {
        i++;
        jobName = conPeek(listOfJobName,i);
        batchJob = null;
        batchJobHistory = null;

        select  *
        from    batchJob
        where   batchJob.Caption == jobName && batchJob.Status == BatchStatus::Waiting;       
        
        numPerJob = 0;
        sumPerJob = 0.0;
        avgValue  = 0.0;
        while 
        select  *
        from    batchJobHistory
            order by    batchJobHistory.EndDateTime desc
            where       batchJobHistory.BatchJobId == batchJob.RecId  &&
                        batchJobHistory.StartDateTime >= utcParmBeginDate && batchJobHistory.EndDateTime <= utcParmEndDate            
        {           
            numPerJob++;
            strCaption      = strFmt('"%1"',batchJob.Caption);
            numOfSecs       = DateTimeUtil::getDifference(batchJobHistory.EndDateTime, batchJobHistory.StartDateTime);
            sumPerJob       += numOfSecs;
            utcStartDate    = DateTimeUtil::applyTimeZoneOffset(batchJobHistory.StartDateTime, Timezone::GMTPLUS0700_BANGKOK_HANOI_JAKARTA);
            utcEndDate      = DateTimeUtil::applyTimeZoneOffset(batchJobHistory.EndDateTime, Timezone::GMTPLUS0700_BANGKOK_HANOI_JAKARTA);
            strStart        = strFmt('%1', utcStartDate);
            strEnd          = strFmt('%1', utcEndDate);        
            strBatchStatus  = strFmt('%1',batchJobHistory.Status);

            // Write to file
            line = [strCaption,
                    strStart,
                    strEnd,
                    strfmt('%1',numOfSecs),
                    strBatchStatus
                   ];
            file.writeExp(line);            
        }    
        avgValue = sumPerJob / numPerJob;
        // Write to file
        line = [strCaption,
                '',
                '',
                strfmt('%1',avgValue),
                ''
                ];
        file.writeExp(line);

        
    }

    info('Done');

}

AX Retail(POS) logical topology

Hi, Retail module of AX 2012 comes up with a lot of details. I'm showing below its logical topology. Without diagram connection, service accounts and etc. make me very headache. :-)

Basically, there are 2 type of connection from/to AX and retail components. Async is for batch transfer, day end transactions, etc. Real-time, as its name, it's for the connection need to do immediately.

The component naming vary to the app version. I'm writing this on AX 2012 R3.

Single store













Multi store

















Monday, June 13, 2016

SSRS - Display zero value

Hi, this post will show the simple way to display a zero value in a SSRS report.

Sometimes, we found the report like this.

What we would like to do are:
Add caption

and then






.....





Eventually, we got the zero value showing in the report.


Until the next post!

Tuesday, May 31, 2016

Retail business concept

A guru in retail business describes me about the overview of Retail business. So let me share it here. (Please note that most of them might not be the wide-use academic terminology)

At high level, I split out the retail operation into 3 main parts.
  1. Buy-in
  2. GR (Goods Receiving)
  3. Sell
Buyer, a subset of Merchandise unit buy products from vendors. Then do receive the products. Merchandise and Marketing will set the price and promotion of each products. The product with retail price will be ready to sell at Store at last. The flow is shown in the figure 1 below.


Figure 1 - Overview of Retail Business



Another concept is Warehouse (W/H) and Distribution center (DC). Products keep at the warehouse, and then transfer to a DC, eventually supply to each stores. Figure 2 illustrates those relations.



Figure 2 - Warehouse and Distribution center



When do GR, the goods can transfer to W/H, DC or store warehouse directly. One of important information which are synced back to the Merchandise unit is the sales amount. This info helps Merchandise to decide which product should buy, what price should set, and etc.


Figure 3 - Receiving the products from a vendor

In addition, there are some concepts interesting, for example Loyalty unit, (or we can call it CRM) the unit which collect sell and customer information to predict or set the proper promotion for each customers.

Hope this article gives an idea about Retail Business. Until the next post!

Friday, May 6, 2016

Fiscal period is not open

I found the issue 'Fical period 5/5/2016 is not open' when I tried to invoice a purchase order yesterday. I solved it by this post Error - Fiscal period for xx/xx/xxxx is not open.


















Above picture is captured from the Internet because I forgot to capture mine ;(

Solution
General ledger -> Setup -> Ledger  

Click  'Ledger calendar'..  make sure that the period you're going to process, the period status is set to 'Open'.














X++ Display Financial Dimension Value

I found this useful post https://sumitsaxfactor.wordpress.com/2011/12/16/getting-individual-dimension-combination-valuesdimension-storage-class-ax-2012/ written by Sumit Loya.

As real beginner on Financial module. This is a simple to show financial dimension from table/class. For example,  General ledger -> Common -> Trial Balance -> Click Journal entries 'Balance' -> Choose a journal and click 'Transaction Origin'.

















Only input of this code is RecId of table GeneralJournalAccountEntry. In short words, this table keeps the key of LedgerAccount and Journal.

--------------------------------------------------------------------------------------------------
static void getDimensionCombinationValues(Args _args)
{
    // DimensionAttributeValueCombination stores the combinations of dimension values
    // Any tables that uses dimension  combinations for main account and dimensions
    // Has a reference to this table’s recid
    DimensionAttributeValueCombination  dimAttrValueComb;
    //GeneralJournalAccountEntry is one such tables that refrences DimensionAttributeValueCombination
    GeneralJournalAccountEntry          gjAccEntry;
    // Class Dimension storage is used to store and manipulate the values of combination
    DimensionStorage        dimensionStorage;
    // Class DimensionStorageSegment will get specfic segments based on hierarchies
    DimensionStorageSegment segment;
    int                     segmentCount, segmentIndex;
    int                     hierarchyCount, hierarchyIndex;
    str                     segmentName, segmentDescription;
    SysDim                  segmentValue;
    ;

    //Get one record for demo purpose
    gjAccEntry = GeneralJournalAccountEntry::find(5642191935);  //put the input here

    setPrefix("Dimension values fetching");
    //Fetch the Value combination record
    dimAttrValueComb = DimensionAttributeValueCombination::find(gjAccEntry.LedgerDimension);
    setPrefix("Breakup for " + dimAttrValueComb.DisplayValue);

    // Get dimension storage
    dimensionStorage = DimensionStorage::findById(gjAccEntry.LedgerDimension);
    if (dimensionStorage == null)
    {
        throw error("@SYS83964");
    }

    // Get hierarchy count
    hierarchyCount = dimensionStorage.hierarchyCount();
    //Loop through hierarchies to get individual segments
    for(hierarchyIndex = 1; hierarchyIndex <= hierarchyCount; hierarchyIndex++)
    {
        setPrefix(strFmt("Hierarchy: %1", DimensionHierarchy::find(dimensionStorage.getHierarchyId(hierarchyIndex)).Name));
        //Get segment count for hierarchy
        segmentCount = dimensionStorage.segmentCountForHierarchy(hierarchyIndex);

        //Loop through segments and display required values
        for (segmentIndex = 1; segmentIndex <= segmentCount; segmentIndex++)
        {
            // Get segment
            segment = dimensionStorage.getSegmentForHierarchy(hierarchyIndex, segmentIndex);

            // Get the segment information
            if (segment.parmDimensionAttributeValueId() != 0)
            {
                // Get segment name
                segmentName = DimensionAttribute::find(DimensionAttributeValue::find(segment.parmDimensionAttributeValueId()).DimensionAttribute).Name;
                //Get segment value (id of the dimension)
                segmentValue        = segment.parmDisplayValue();
                //Get segment value name (Description for dimension)
                segmentDescription  = segment.getName();
                info(strFmt("%1: %2, %3", segmentName, segmentValue, segmentDescription));
            }
        }
    }
}
--------------------------------------------------------------------------------------------------

The result looks like below.



Tuesday, April 26, 2016

X++ Get table property

This simple code illustrates the table property by X++.

static void Job19(Args _args)
{
    SysDictTable    sysDictTable;  
    TreeNode        treeNode;
    TableName       tableName = 'VendTable';
    TableId         tableId;
    ;
 
    tableId         = tableName2Id(tableName);
    sysDictTable    = new SysDictTable(tableId);
    treeNode        = sysDictTable.treeNode();
 
    info(strFmt('%1',tableName));
    info(strFmt('%1',tableId));  
    info(strFmt('%1',treeNode.AOTgetProperty("CreatedDateTime")));
    info(strFmt('%1',treeNode.AOTgetProperty("SaveDataPerCompany")));
}









































Tuesday, April 19, 2016

X++ Get the list of user with role

Hi, if we would like to display the user list with its role, we can use a kind of the following code. It isn't quite a good code as we should join all tables in the while select statement, however I got the display with '@sys..' and not found the solution yet, so I change a bit back to basic by adding one more select in it.

static void getUserWithRole(Args _args)
{
    UserInfo            userInfo;
    SecurityUserRole    securityUserRole;
    SecurityRole        securityRole;
   
    int i=0;
    ;

    while
    select userInfo
        join    securityUserRole
        where   securityUserRole.User == userInfo.Id
    {
        i++;
        if (i >= 5)
            break;
       
        info(userInfo.Id);    
       
        securityRole = null;
        select securityRole
        where  securityRole.RecId == securityUserRole.SecurityRole;
       
        if (securityRole)
        {
            info(securityRole.AotName);
            info(securityRole.Name);          
            info(securityRole.Description);                      
        }  
    }
}

The result if we write it in csv format, will looks like below. Until the next post!

Tuesday, April 12, 2016

X++ Create task to monitor batch running overnight and send email

Good morning! In real-life we have many batches which are scheduled to run overnight. This scenario will show how to create a task to check their status and send email to related supporters.

There are two sub tasks for this. They are:
1. Batch status checking
2. Sending email
3. Schedule task and configuration

Let's start with the class and other methods.

class batchMonitor extends RunBaseBatch
{
    boolean isCompleted;
    #define.filename(@'E:\aaa\batch.csv')


public container pack()
{
    return conNull();
}

public boolean unpack(container packedClass)
{
    return true;
}

public void run()
{
    this.genCSV();
    this.sendEmail();
}

} // end class


1. Batch status checking

This method will check batch status and write csv.

private void genCSV()
{
    // ------------- Declaration -------------
    BatchJob            batchJob;
    BatchJobHistory     batchJobHistory;
    BatchCaption        jobName;

    int                 i=0,
                        iMax=0;

    container           listOfJobName   = ['B001', 'B002'];

    str                 strCaption,
                        strRecurPeriod,
                        strBatchStatus;

    // Declaration: CSV file
    CommaTextIo         file;
    container           line,
                        lineHeader      = ['B001', 'B002'];
    #File
    ;


    // ------------- Process -------------
    isCompleted = true;

    // Check file availability
    file = new CommaTextIo(#filename, #io_write);
    if (!file || file.status() != IO_Status::Ok)
    {
       throw error("File cannot be opened.");
    }

    // Write header
    line = lineHeader;
    file.writeExp(line);

    // Write line
    iMax = conLen(listOfJobName);
    while (i < iMax)
    {
        i++;
        jobName = conPeek(listOfJobName,i);
        batchJob = null;
        batchJobHistory = null;

        select  *
        from    batchJob
        where   batchJob.Caption == jobName && batchJob.Status == BatchStatus::Waiting
            join        batchJobHistory
            order by    batchJobHistory.EndDateTime desc
            where       batchJobHistory.BatchJobId == batchJob.RecId;

        strCaption      = strFmt('"%1"',batchJob.Caption);
        strRecurPeriod  = batchJob.recurrenceText();
        strBatchStatus  = strFmt('%1',batchJobHistory.Status);

        if (strBatchStatus != 'Ended')
            isCompleted = false;

        // Write to file
        line = [strCaption,
                strRecurPeriod,
                strBatchStatus
               ];
        file.writeExp(line);

    }

    file.finalize();

}

2. Sending email

This method will send an email.

private void sendEmail(Args _args)
{
    str                                   sender    = 'test@test.com';  // mail id of the sender
    str                                   recipient = 'test@test.com';  // mulitple recipients can be specified
    str                                   cc        = 'test@test.com';  // mulitple cc id's can be specified
    str                                   subject   = 'Subject of the mail';
    str                                   body      = 'Content of the mail';

    List              toList;
    List              ccList;
    ListEnumerator    enumList;
    Set               permissionSet;
    System.Exception  exception;

    str                                     mailServer;
    int                                     mailServerPort;
    System.Net.Mail.SmtpClient              mailClient;
    System.Net.Mail.MailMessage             mailMessage;
    System.Net.Mail.MailAddress             mailFrom;
    System.Net.Mail.MailAddress             mailTo;
    System.Net.Mail.MailAddressCollection   mailToCollection;
    System.Net.Mail.MailAddressCollection   mailCCCollection;
    System.Net.Mail.AttachmentCollection    mailAttachementCollection;
    System.Net.Mail.Attachment              mailAttachment;
    ;
 
    //Prepare init value
    subject     = strFmt('Batch job status as of %1',today());
    body        = strFmt('Hi all\r\n\rPlease kindly see the batch job status as of %1 in the attached. The status is',today());
    if (isCompleted)
    {
        subject += ' - Completed';
        body += ' all completed.';
    }
    else
    {
        subject += ' - Failed';
        body += ' failed.';
    }
    body += '\r\n\rRegards\r\n\rMisterAAA';  

    try
    {
        toList = strSplit(recipient, ';');
        ccList = strSplit(cc, ';');

        permissionSet = new Set(Types::Class);
        permissionSet.add(new InteropPermission(InteropKind::ClrInterop));
        permissionSet.add(new FileIOPermission(#filename, 'test1'));
        CodeAccessPermission::assertMultiple(permissionSet);

        mailServer      = SysEmaiLParameters::find(false).SMTPRelayServerName;
        mailServerPort  = SysEmaiLParameters::find(false).SMTPPortNumber;
        mailClient      = new System.Net.Mail.SmtpClient(mailServer, mailServerPort);

        enumList = toList.getEnumerator();
        enumList.moveNext();

        mailFrom        = new System.Net.Mail.MailAddress(sender);
        mailTo          = new System.Net.Mail.MailAddress(strLTrim(strRTrim(enumList.current())));
        mailMessage     = new System.Net.Mail.MailMessage(mailFrom, mailTo);

        mailToCollection = mailMessage.get_To();
        while (enumList.moveNext())
        {
            mailToCollection.Add(strLTrim(strRTrim(enumList.current())));
        }

        enumList            = ccList.getEnumerator();
        mailCCCollection    = mailMessage.get_CC();
        while (enumList.moveNext())
        {
            mailCCCollection.Add(strLTrim(strRTrim(enumList.current())));
        }

        mailMessage.set_Priority(System.Net.Mail.MailPriority::Normal);
        mailMessage.set_Subject(subject);
        mailMessage.set_Body(body);

        mailAttachementCollection   = mailMessage.get_Attachments();
        mailAttachment              = new System.Net.Mail.Attachment(#fileName);
        mailAttachementCollection.Add(mailAttachment);

        mailClient.Send(mailMessage);
        mailMessage.Dispose();

        CodeAccessPermission::revertAssert();

        info("Email sent successfully");
    }
    catch (Exception::CLRError)
    {
        exception = ClrInterop::getLastException();
        while (exception)
        {
            info(exception.get_Message());
            exception = exception.get_InnerException();
        }
        CodeAccessPermission::revertAssert();
    }

}

3. Schedule task and configuration

     3.1 Create a folder on AOS server  - This is important as Batch is being run on Server side so we need a folder to keep the attachment file. In this case, it's E:\aaa.
     3.2 Check Outgoing email parameter

     3.3 Check Batch server parameter

     3.4 Schedule the class by the following job

static void batchSchedule(Args _args)
{
    BatchHeader     batchHeader;
    BatchInfo       batchInfo;
    RunBaseBatch    rbbTask;
    str sParmCaption = "Batch Monitoring";
    ;

    rbbTask     = new batchMonitor();
    batchInfo   = rbbTask.batchInfo();
    batchInfo.parmCaption(sParmCaption);
    batchInfo.parmGroupId(""); // The "Empty batch group".
    batchHeader = BatchHeader ::construct();
    batchHeader.addTask(rbbTask);
    batchHeader.save();
    info(strFmt("'%1' batch has been scheduled.", sParmCaption));
}

Finally, our task is in the batch list which can manage through dynamics ax standard. Thanks for reading. Until next post!

References:
Walkthrough: Extending RunBaseBatch Class to Create and Run a Batch [AX 2012]
Sending emails with CC and attachment in AX 2012

Monday, April 4, 2016

X++ Date and utcDateTime

I will consolidate the techniques relating to Date and utcDateTime here.

Conversion: Date to utcDateTime

Refer to Date to UTCDateTime Convertion Dynamics Ax 2012

Sometimes we might need to query the data on field 'createdDateTime' whose type is utcDateTime so this code will help to convert type Date to utcDateTime.

Value 0 to 86400 stand for the time period 00:00:00 to 23:59:59

date            exampleDate = 01\02\2015;    // it's 01-Feb-2015
utcDateTime     utcStartDate,
                utcEndDate;
CustTable       custTable;
;

utcStartDate  = DateTimeUtil::newDateTime(startDate,0);
utcEndDate    = DateTimeUtil::newDateTime(endDate,86400);

select  count(RecId)
from    custTable
where   custTable.createdDateTime >= utcStartDate &&
        custTable.createdDateTime <= utcEndDate;

-------------------------------------------------------------------------------------------------------------

utcDateTime: how to increase or decrease datetime value

Sometimes we might need to query the data on the specific range. This below would help to get idea to increase or decrease utcDateTime.

utcDateTime     currentday,
                yesterday;
;
    
currentday  = DateTimeUtil::utcNow();
yesterday   = DateTimeUtil::addDays(currentday, -1);    
yesterday   = DateTimeUtil::addSeconds(yesterday, 1);
    
info(strFmt('%1',currentday));
info(strFmt('%1',yesterday));










-------------------------------------------------------------------------------------------------------------

Conversion: numeric to Time (TimeOfDay)

we can convert numeric (0 - 86400) to TimeOfDay by following code.

TimeOfDay   aaa;
;
    
aaa = 3600;
info(time2Str(aaa, TimeSeparator::Colon, TimeFormat::AMPM));
    
aaa = 4200;
info(time2Str(aaa, TimeSeparator::Colon, TimeFormat::AMPM));
    
aaa = 10800;
info(time2Str(aaa, TimeSeparator::Colon, TimeFormat::AMPM)); 

Wednesday, March 23, 2016

SSRS - one or more projects in the solution were not loaded correctly

Hi, even though this error is a simple and actually found at the beginning only when start report developing however I think it would be ok as well if I logged it in the forum.

 --- Issue ---
In the multiple AOS environments, we create a SSRS report in Visual Studio and then save and add it back to AOT. Next day, we open AOT and try to edit that VS Studio project. But found the error like this.
one or more projects in the solution were not loaded correctly.
C:\Users\..\Microsoft Dynamics AX\Dynamics AX Model Projects\ShReleaseProductReport\ShReleaseProductReport.dynamicsproj : error : Unable to connect to the AOS specified in the Microsoft Dynamics AX Client Configuration. The configuration could be missing, invalid, or the AOS is not running. To connect to the AOS, check the network connection between the client and the AOS, verify that a valid configuration exists, and that the AOS service is running on the server.

--- Resolution ---
- Open AX 2012 Client configuration
- At 'Set Configuration Store' --> Load the correct client axc file
- It will show as follows.

Friday, March 18, 2016

SQL Server: Using SqlCmd to export table (including header) to CSV

SQLCMD is a tool which can export dynamics AX 2012 table to CSV easily. Let's see.

sqlcmd  -E -Q "select * from AX2012_Train.dbo.BANKPARAMETERS" -o "e:\test\test.csv" -s","
Until the next post! :-)

SQL Server: Using BCP to export table (including header) to CSV

If we have to export a table to CSV file. There is a simple way to do this by BCP. 

BCP "DECLARE @colnames VARCHAR(max);SELECT @colnames = COALESCE(@colnames + ',', '') + column_name 
from AX2012_Train.INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='BANKPARAMETERS'; 
select @colnames;" queryout e:\test\HeadersOnly.csv -c -T 

BCP AX2012_Train.dbo.BANKPARAMETERS out e:\test\TableDataWithoutHeaders.csv -c -t, -T 

copy /b e:\test\HeadersOnly.csv+e:\test\TableDataWithoutHeaders.csv e:\test\TableData.csv

del e:\test\HeadersOnly.csv
del e:\test\TableDataWithoutHeaders.csv

However this way isn't a best solution to extract table in Dynamics AX 2012 because the column of AX table doesn't sort in the ascending order. (Some field such as RecId, DataAreaId will be put at the last)  But I'm sure this code will be useful in the general case.

SQL Server: Display table name

Hi, today I will show the simple query which run on Query editior on MS SQL Server Management studio. It displays the table names in Dynamics AX database.

select *
from INFORMATION_SCHEMA.TABLES
where TABLE_NAME like '%parameters%' and
    TABLE_NAME not like '%_BE' and
   TABLE_NAME not like '%_BR' and
   TABLE_NAME not like '%_CN' and
   TABLE_NAME not like '%_DE' and
   TABLE_NAME not like '%_NL' and
   TABLE_NAME not like '%_RU' and
   TABLE_NAME not like '%_UK';


Tuesday, March 15, 2016

X++ Deal with a table buffer as a variable (part2)

Refer to MSDN page Table as data type

We can also write or refer to a field as a variable like as below code.


public void printCust()
{
    int i, n, k;
    CustTable custTable;
    DictTable dictTable;
    ;
 
    dictTable = new DictTable(custTable.TableId);
    n = dictTable.fieldCnt();
 
    print "Number of fields in table: ", n;
    for(i=1; i<=n; i++)
    {
        k = dictTable.fieldCnt2Id(i);
        print "The ", dictTable.fieldName(k), 
            " field with Id=",k, " contains '", 
            custTable.(k), "'";
        pause;
    }
}



X++ Deal with a table buffer as a variable

Sometimes we got some requests which need to write the same select statement or query for many tables. This below simple code might be useful.


static void Job16(Args _args)
{
    SysDictTable    dictTable;
    Common          common;    
    str             tblName;
    ;
    
    tblName     = 'InventTable';
    dictTable   = SysDictTable::newName(tblName);
    common      = dictTable.makeRecord();

    select count(RecId) from common;
    info(strFmt('All records of %1 are %2',tblName,common.RecId));    
}


Thanks, Martin DrĂ¡b from this post String value as tableName in Select Statement

Tuesday, March 8, 2016

X++ Useful shortcut & hot keys

Here, the shortcut & hot keys which might be useful. Will try to update when find some more interesting.



Mark comment                         Ctrl + e + c
Unmark comment                     Ctrl + e + u

Monday, March 7, 2016

X++ Get stock on hand on an item

Refer to AX2012: X++ code to get on hand on an item Priyanka Agarwal gave an excellent clarification about getting stock on hand by given item, and dimension.

Below example shows the simple code to retrieve stock on hand on an item, without given any inventory dimensions.

    
static void Job12(Args _args)
{
    InventTable inventTable;
    ;
    
    while
    select firstOnly10 ItemId
        from inventTable
        index ItemIdx
    {
        info(inventTable.ItemId);
        info(strFmt('%1',InventOnhand::newItemId(inventTable.ItemId).availPhysical()));    
    }
}

X++ Consideration for Change Company Using

Refer to ChangeCompany and table buffers

Be careful when using 'changeCompany'. We have to clear the table buffer unless the company will not be changed as we expected. This happens when we use the same name of table buffer.

See below example.

    changeCompany('A')
    {
        while
        select  ModelGroupId
        from    inventModelGroup
        index   GroupIdx
        {
         ...
         ...
        }
    }
    changeCompany('B')
    {
        inventModelGroup = null;        //Need this statement to clear table buffer
        while
        select  ModelGroupId
        from    inventModelGroup
        index   GroupIdx
        {
         ...
         ...
        }
    }

X++ Create a simple CSV file (part 2)

Hi I found another way to write CSV in a job which is simpler than the previous one I posted.

Refer to Santosh Kumar Singh's blog

Let say I re-post Santosh Kumar Singh's code again with a bit layout adjust. Thanks him for a nice code.


CommaTextIo file;
container line;
CustTable custTable;
#define.filename(@'C:\Test\TestFile.csv')
#File
;

file = new CommaTextIo(#filename, #io_write);
if (!file || file.status() != IO_Status::Ok)
{
   throw error("File cannot be opened.");
}

while select AccountNum, Name from custTable
{
   line = [custTable.Name,
           custTable.Name];
   file.writeExp(line);
}

info(strFmt("File %1 created in specified folder.", #filename));
}