Tuesday, October 12, 2010

DAX Blog: Add new Inventory Storage Dimension

AX has 8 standard inventory dimension here is the classification of these 8 dimensions
Inventory Item Dimensions
  • Size
  • Colour
  • Configuration
Inventory Storage Dimensions
  • Batch
  • Serial
  • Warehouse
  • Location
  • Pallet
Below is the list of objects that needs to be created or modified while creating a new inventory storage dimension. Please note that the list is not exhaustive enough but contains most of the objects that needs to be modified. Let us assume the name of the new dimension would be 'Test'

http://www.axaptapedia.com/Image:SharedProject_InventTest.xpo

Poping color selection window.

Here is an interesting piece of code for poping color selection window.
 
static void colors(Args _args)
{
    #DEFINE.COLORVALUE (64)
    int             r, g, b;
    container       chosenColor;
    Binary          customColors        = new Binary(#COLORVALUE);
    CCColor         colorValue;

    chosenColor = WinAPI::chooseColor(infolog.hWnd(), r, g, b, customColors, true);
    if (chosenColor)
    {
        [r, g, b] = chosenColor;
        colorValue  = WinAPI::RGB2int(r, g, b);
        print (colorValue);
        pause;
    }
}
 
Below is the window that pops up
untitled

Axapta queries Fact

The query object model contains classes to define and run a query. These objects are used to define the query data source, the fields returned, record ranges and relations to child data sources. The following illustration shows the object model.
QueryModel
 
The query components shown in the previous figure are system classes. The query classes are more visible when you create a dynamic query in code, but they are also used behind the scenes when you create a static query in the AOT.
System class
Description
QueryRun
Executes the query and fetches the data.
Query
The top level of the query definition. This class holds some properties itself and has one or more related data sources.
QueryBuildDataSource
Defines access to a single data source in the query. If more than one data source exists at the same level in a query, they result in separate SQL statements that are executed sequentially. If one data source exists as a child of another data source, a join is created between the two data sources.
QueryBuildFieldList
Defines which fields are returned from the database. The default is that the field list is dynamic, which returns all fields from the data source table, map, or view. Each data source has only one QueryBuildFieldList object, which contains information on all selected fields. It's possible to specify aggregate functions like SUM, COUNT, and AVG on the field list object.
QueryBuildRange
Defines a subset of records returned based on a single field. A range is translated into a WHERE clause in the query SQL statement. If more than one field is used to limit the query (WHERE clause), the data source will contain more than one range.
QueryBuildDynalink
Contains information regarding a relation (limitation) to an external record. When the query is run, this information is converted to additional entries in the WHERE clause of the query SQL statement. Can only exist on the parent data source of a query. The function is used by forms, when two data sources are synchronized. Then the child data source will contain a dynalink or dynalinks to the parent data source. The function is used even if the two data sources are placed in two different forms but are still synchronized.
QueryBuildLink
Specifies the relation between the two data sources in the join. Can only exist on a child data source.
[Source for above text: Microsoft Dyanmics AX 4.0 Developer Guide]
Below is a job that can illustrate usage different query framework classes
static void queryCreation(Args _args)
{
    Query                   query = new Query('MyQuery');
    QueryBuildDataSource    custTableQBDS, custTransQBDS;
    QueryBuildRange         qbr1, qbr2;
    QueryBuildFieldList     qbFl, qbF2, qbF3;
    QueryBuildLink          qbl;
    QueryBuildDynalink      qbdl;
    QueryRun                qr;
    CustTable               custTable;
    CustTrans               custTrans;
    LedgerTrans             ledgerTrans;
    ;
    custTableQBDS = query.addDataSource(tablenum(custTable));
    //This will enable you to add the fields you like
    //If dynamics is yes all the fields will be automatically added

    custTableQBDS.fields().dynamic(NoYes::No);
    qbFl = custTableQBDS.fields().addField(fieldNum(CustTable, AccountNum));
    qbF2 = custTableQBDS.fields().addField(fieldNum(CustTable, InvoiceAccount));
    qbF3 = custTableQBDS.fields().addField(fieldNum(CustTable, CustGroup));
    //Adding Ranges
    qbr1 = query.dataSourceTable(tableNum(CustTable)).addRange(fieldNum(CustTable, AccountNum));
    qbr2 = query.dataSourceTable(tableNum(CustTable)).addRange(fieldNum(CustTable, InvoiceAccount));
    qbr1.value(SysQuery::value('4011'));
    qbr2.value(SysQuery::value('4010'));
    //Add child data source to main datasource
    custTransQBDS = custTableQBDS.addDataSource(tableNum(CustTrans));
    //Two ways to add relation
    //First - This may not work in all the scenarios
    custTransQBDS.relations(true);
    //Second - QueryBuildLink
    qbl = custTransQBDS.addLink(fieldNum(CustTable, AccountNum), fieldNum(CustTrans, AccountNum));
    SetPrefix ('Queries');
    setPrefix ('Dynalinks');
    qr = new QueryRun(query);
    setPrefix ('Without');
    while (qr.next())
    {
        if (qr.changed(tableNum(CustTable)))
        {
            custTable = qr.get(tableNum(CustTable));
        }
        setPrefix ('Customer - ' + custTable.AccountNum);
        if (qr.changed(tableNum(CustTrans)))
        {
            custTrans = qr.get(tableNum(CustTrans));
            info(custTrans.Voucher);
        }
    }
    //Dynalink test so run the loop after dynamically linking with a table    select firstonly ledgerTrans where ledgerTrans.Voucher == 'US_800006' &&
            ledgerTrans.AccountNum == '130100';
    custTransQBDS.addDynalink(fieldNum(CustTrans, Voucher), ledgerTrans, fieldNum(LedgerTrans, Voucher));
    custTransQBDS.addDynalink(fieldNum(CustTrans, TransDate), ledgerTrans, fieldNum(LedgerTrans, TransDate));
    qr = new QueryRun(query);
    setPrefix('Dynalinks');
    setPrefix ('With');
    while (qr.next())
    {
        if (qr.changed(tableNum(CustTable)))
        {
            custTable = qr.get(tableNum(CustTable));
        }
        setPrefix ('Customer - ' + custTable.AccountNum);
        if (qr.changed(tableNum(CustTrans)))
        {
            custTrans = qr.get(tableNum(CustTrans));
            info(custTrans.Voucher);
        }
    }
}

Send Email in AX 4.0

In this article, I am going to demonstrate different email techniques that can be used in AX 4.0. Following classes can be used to send an email


* Mapi and its related classes
* SysMailer
* SysInetMail
* SysEmailBatch
* SmmOutlookEmail

MAPI technique:

Following code demonstrates the usage of mapi class for sending email. It uses outlook to send mail.

static void emailThruMapi(Args _args)
{
MapiEx mapiEx;
MapiExMail mapiExMail;
boolean mapiInitialised;
COM outlook;
COM item;
COM outlookNameSpace;
COM folder;
COM attachments;
str storeId;
str entryId;

#define.outlookapplication('Outlook.Application')
#define.mapi('Mapi')
#smmMSOutlook2002ObjectModelConstants
#define.htmlText('Hi There')
;

outlook = new COM (#outlookapplication);
outlookNameSpace = outlook.getNameSpace(#mapi);
outlookNameSpace.logon();

folder = outlookNameSpace.getDefaultFolder(#olFolderInbox);
item = outlook.createItem(#olMailItem);
storeId = folder.storeId();

mapiEx = new MapiEx();

if(mapiEx && mapiEx.mapiInitialised())
{
mapiInitialised = true;
if (!mapiEx.logon("","",0) || !mapiEx.openMessageStore(storeId))
{
mapiInitialised = false;
mapiEx.logout();
mapiEx.finalize();
}

//To send mail in HTML format
item.bodyFormat(#olFormatHTML);
item.htmlBody(#htmlText);

//To send mail in plain text format
//item.body('Hi There');

item.subject('Test mail');

//----Attachements-------------------
attachments = item.attachments();
attachments.add('E:\\Test\\4000.pdf', 1, 1, '4000.pdf');

item.saveSentMessageFolder(outlookNameSpace.getDefaultFolder(#olFolderSentMail));
item.save();
entryId = item.entryId();

mapiExMail = mapiEx.getMailFromEntryId(entryId);
if (!mapiExMail)
{
mapiInitialised = false;
mapiEx.logout();
mapiEx.finalize();
}
}

if(item)
{
if (mapiInitialised && mapiExMail)
{
//TO
mapiExMail.addRecipient('avnish.chandra@gmail.com', "", #olTo);
//CC
mapiExMail.addRecipient('avnish.chandra@gmail.com',"",#olCC);
//BCC
mapiExMail.addRecipient('avnish.chandra@gmail.com',"",#olBCC);

try
{
mapiExMail.save();
mapiExMail.close();
mapiExMail.finalize();
item = outlookNameSpace.getItemFromID(strupr(entryId));

//This will display the mail item
//item.display();

//This will directly send the mail without poping the mail window
item.send();
}
catch
{
if (mapiInitialised)
{
mapiEx.logout();
mapiEx.finalize();
}

// An error occured sending mail from outlook.
throw error("@SYS97460");
}
}
}
}

SysMailer:


In the following code you can see how to use SysMailer class for sending mails. To use SysMailer class you need to set Relay server or computer name, user name and password in Administration --> Setup --> Email parameters form. This class internally uses CDO.Message dll for communication purposes. Please note in AX 3.0 SysMailer uses Dundas.Mailer dll for communication.

static void emailThruSysMailer(Args _args)
{
SysMailer mailer = new SysMailer();
SysEmailParameters parameters = SysEmailParameters::find();
;

if (parameters.SMTPRelayServerName)
{
mailer.SMTPRelayServer(parameters.SMTPRelayServerName,
parameters.SMTPPortNumber,
parameters.SMTPUserName,
SysEmailParameters::password(),
parameters.NTLM);
}
else
{
mailer.SMTPRelayServer(parameters.SMTPServerIPAddress,
parameters.SMTPPortNumber,
parameters.SMTPUserName,
SysEmailParameters::password(),
parameters.NTLM);
}

mailer.fromAddress('avnish.chandra@gmail.com');
mailer.tos().appendAddress('avnish.chandra@gmail.com');
mailer.body('hi');
mailer.sendMail();
}

SysInetMail:

SysInetMail internally uses Mapi framework only. But to use SysInetMail one has to setup email templates from Basic --> Setup --> Email templates. SysInetMail will automatically pick sender id, subject, sender name, email text etc. from the email template that you provide while sending from SysInetMail. If you provide a full email address and not the id from Email templates table then also mail will be sent but in that case you need to provide the details yourself.

static void emailThruSysInetMail(Args _args)
{
SysInetMail mail = new SysInetMail();
;

//To send to an email address directly
mail.sendMailAttach('avnish.chandra@gmail.com', 'avnish.chandra@gmail.com', 'Test e-mail', 'Hi ', false, 'C:\\Test\\custStmt.pdf');

//To use an email template to send mail
SysInetMail::sendEMail('Alerts', 'en-us', 'avnish.chandra@gmail.com');
}

SysEmailBatch:


SysEmailBatch internally uses SysMailer class and is used to send emails in batch. That is this class is batchable. Here is a small example for the class

static void emailThruSysEmailBatch(Args _args)
{
SysEmailBatch batch = new SysEmailBatch();
;

batch.parmSenderAddr('avnish.chandra@gmail.com');
batch.parmEmailAddr('avnish.chandra@gmail.com');
batch.parmMessageBody('Hello');
batch.parmSubject('Test email');
batch.run();
}

SmmOutlookEmail:

This class internally uses Mapi class and is extensively used in HRM module. One feature of this class is that we can specify email groups and it can send mails to all the members defined under this email group. Here is a sample code

static void emailThruSmmOutlookEmail(Args _args)
{
SmmOutlookEmail smmOutlookEmail = new SmmOutlookEmail();
;

if (smmOutlookEmail.createMailItem())
{
smmOutlookEmail.addEMailRecipient('avnish.chandra@gmail.com');
smmOutlookEmail.addSubject('Test email');
smmOutlookEmail.isHTML(true);
smmOutlookEmail.addBodyText('Hi How are you?');
smmOutlookEmail.sendEMail(smmSaveCopyOfEmail::No);
}
}

RegisterBatchOrderPickList from x++

static void RegisterBatchOrderPickList(Args _args)
{
ProdJournalCheckPostProd _prodJournalCheckPostProd;
ProdJournalCheckPostBOM _prodJournalCheckPostBOM;
prodJournalTable _prodJournalTable;
ProdJournalBom _prodJournalBom;
;

SELECT _prodJournalTable WHERE _prodJournalTable.JournalId == "00000168";
ttsbegin;

WHILE SELECT FORUPDATE _prodJournalBom WHERE _prodJournalBom.JournalId == "00000168"
{
if(_prodJournalBom.InventTransId == "000003069")
{
_prodJournalBom.InventConsump = 21;
_prodJournalBom.update();
}
}

ttscommit;

_prodJournalCheckPostBOM = ProdJournalCheckPostBOM::newPostJournal("00000168", FALSE);
_prodJournalCheckPostBOM.postProdJournalTableBOM();
_prodJournalCheckPostBOM.run();
}

RegisterPickList from x++ code

static void RegisterPickList(Args _args)
{

//Transfer Order Shipment

InventDim _InventDim;
WmsOrdertrans _WmsOrdertrans;
WmsPickingRoute _WmsPickingRoute;
Map selectedLines = new Map(Types::Int64,Types::Container);

_InventDim.inventSiteId = "NAR";
_inventDim.inventBatchId = "000000428";
_inventDim.InventLocationId = "NARS1";
_inventDim.WmsLocationId = "Silo105";

_inventDim = InventDim::findOrCreate(_inventDim);

ttsbegin;

SELECT FORUPDATE _WmsOrdertrans WHERE _WmsOrdertrans.inventTransId == "000003231"
JOIN _WmsPickingRoute WHERE _WmsPickingRoute.pickingRouteID == _WmsOrdertrans.routeId;
//We have more available on line than needed

_WmsOrdertrans.qty = 1;
_wmsOrderTrans.inventDimId = _inventDim.inventDimId;
_WmsOrdertrans.update();

_WmsOrdertrans.qty = 9;
_wmsOrderTrans.inventDimId = _inventDim.inventDimId;
_WmsOrdertrans.update();
selectedLines.insert(_WmsOrdertrans.RecId,[_WmsOrdertrans.OrderId,_WmsOrdertrans.RecVersion]);

ttscommit;
WMSOrderTransType::finishPickingLinesMulti(selectedLines.pack(),_WMSPickingRoute.CurrentPickPalletId,_WMSPickingRoute.Operator);
}

Monday, October 11, 2010

Custom job to have the AIF run instantly at the click of a button,

static void runAIFReceive(Args _args)
{
AifGatewayReceiveService aifGatewayReceiveService;
AifInboundProcessingService aifInboundProcessingService;
;

aifGatewayReceiveService = new AifGatewayReceiveService();
aifGatewayReceiveService.run();
aifInboundProcessingService = new AifInboundProcessingService();
aifInboundProcessingService.run();
}