Sunday, December 21, 2008

Singleton pattern

A singleton is a class which only allows a single instance of itself to be created. An instance of a singleton class is not created until the first time it is requested.
Usually, singleton classes use a static variable to hold a reference their own instance, but as Axapta does not support static variables we are forced to use the global caching mechanism to achieve the same effect. Due to the nature of Axapta global caching, we need to add the instance reference to both the client- and server-side caches separately.
For safety, it should be impossible for an instance of a singleton class to be created directly (without using the accessor static method). We should therefore override the new() method of our singleton class and use the private access modifier to ensure that it cannot be instantiated directly.
The following static method can be modified and added to a class to make it a singleton.
// RunOn:CalledFrom
public static SingletonTest instance()
{
    SingletonTest   singleton;
    SysGlobalCache  globalCache = infolog.objectOnServer() ? appl.globalCache() : infolog.globalCache();
    ;
 
    if (globalCache.isSet(classStr(SingletonTest), 0))
        singleton = globalCache.get(classStr(SingletonTest), 0);
    else
    {
        singleton = new SingletonTest();
        infoLog.globalCache().set(classStr(SingletonTest), 0, singleton);
        appl.globalCache().set(classStr(SingletonTest), 0, singleton);
    }
 
    return singleton;
}
We should also make the following change, adding the private keyword to the constructor to stop it being instantiated from outside the instance() method
private void new()
{
}

Mandatory DialogField

When adding controls to a standard form, either bound to a data field or not, one useful property is the Mandatory property. If this is set, then the control is shown with a red squiggly line if no value has been entered, and an error will automatically be shown when the user tries to save the record or close the form. This article describes how to also make this functionality available from dialog forms. You need to define the mandatory property in a DialogField class, as shown below.
1. Add mandatory() method into DialogField class:
// Created by GRR on 01.11.2005
void mandatory(boolean r)
{
    str name;
    // If properties exists then we are on server
    if (properties)
    {
        name = #Propertymandatory;
        if (! properties.exists(name))
            properties.add(name,true);
        properties.value(name,r);
    }
    else
        this.fieldControl().mandatory(r);
}
2. Add into unpack() method following lines:
case #PropertyMandatory:
    this.mandatory(unpackedProperties.valueIndex(i));
    break;

Determining if an object is a subclass

It can sometimes be useful to determine if an object is a instance of a subclass of some specified parent class.
While over-use of this probably suggests a class framework which has not been well designed, it is valuable in specific instances.
There are two static methods on the SysDictClass which can be used for this purpose:

static boolean isSuperclass(classId  _id, classId  _potentialAncestorId)

This method checks if the classId passed in _id is a subclass of the _potentialAncestorId
static boolean isEqualOrSuperclass(classId  _id, classId  _potentialAncestorId)
This method checks if the classId passed in _id is a subclass of the _potentialAncestorId, and will also return true if _id equals _potentialAncestorId

The following code uses the SalesTableType heirarchy as an example.
SalesTableType          stt;
SalesTableType_Journal  stt_j;
;
 
// This is true as stt is an ''instance'' of SalesTableType
if (SysDictClass::isEqualOrSuperclass(classidget(stt), classnum(SalesTableType)))
{
    // Code here will be run
}
 
// This is true as stt_j is a ''subclass'' of SalesTableType
if (SysDictClass::isEqualOrSuperclass(classidget(stt_j), classnum(SalesTableType)))
{
    // Code here will be run
}
 
// This is true as stt_j is an ''instance'' of SalesTableType_Journal
if (SysDictClass::isEqualOrSuperclass(classidget(stt_j), classnum(SalesTableType_Journal)))
{
    // Code here will be run
}
 
// This is FALSE as stt is NOT an instance or subclass of SalesTableType_Journal
if (SysDictClass::isEqualOrSuperclass(classidget(stt), classnum(SalesTableType_Journal)))
{
    // Code here will NOT be run
}

Set Class

Set consist of a data set that contains values of the same type, where value is unique.

A Set is alway sorted on the value.

Define

Set s = new Set(Types::STRING);

Insert a value

s.add("Wassini");

s.add("Eric");

Exists value

To see if a value already is added, use the in method:

if (s.in("Wassini"))

   print "Yes!";

else

   print "No!";

Getting values

There are several ways to get the values in the set.

Using a SetIterator

Using a SetEnumerator

SetIterator

The SetIterator loops throug the complete set:

SetIterator si;

 

si = new SetIterator(s);

 

while (si.more())

{

  print si.value();

 

  si.next();

}

SetEnumerator

SetEnumerator class is like SetIterator class, but allows the deletion of elements during enumeration and SetIterator does not.

SetEnumerator se=s.getEnumerator();

 

while (se.moveNext())

{

  print se.current();

}

Removing values

Just use the remove method to remove the active value.

s.remove("Wassini");

Other methods

// Get number of elements:

print s.elements(); 

 

// Get the used types:

print s.definitionString(); 

 

// Dump the whole set as a string:

print s.toString();

Map Class

Map (Foundation class) consist of a data set that contains a key and a corresponding value, where the key is unique. The key and the value need not be from the same data type.
A Map is alway sorted on the key value.
Contents [hide]
1 How to use
1.1 Define
1.2 Insert a value
1.3 Exists value
1.4 Getting values
1.4.1 MapIterator
1.4.2 MapEnumerator
1.4.3 Direct method
1.5 Removing values
1.6 Updating values
1.7 Other methods
2 See also
How to use
Define
Map m = new Map(Types::STRING, Types::INTEGER); Insert a value
m.insert("Wassini", 37);
m.insert("Eric", 102); Exists value
To see if a value already is added, use the exists method:
if (m.exists("Wassini"))
print "Yes!";
else
print "No!"; Getting values
There are several ways to get the values in the map.
Using a MapIterator
Using a direct method
MapIterator
The MapIterator loops throug the complete map:

MapIterator mi;
mi = new MapIterator(m);
while (mi.more())
{
print mi.key();
print mi.value();
mi.next();
} MapEnumerator
MapEnumerator class is like MapIterator class, but allows the deletion of elements during enumeration and MapIterator does not.
MapEnumerator me = m.getEnumerator();
while (me.moveNext())
{
print me.currentKey();
print me.currentValue();
} Direct method
It is possible to get find a specific value from the key:

print m.lookup("Wassini"); Removing values
Just use the remove method to remove the active key/value pair.

m.remove("Wassini"); Updating values
It is not possible to update a value directly:
int age;
str keyid = "Wassini";
age = m.exists(keyid) ? m.lookup(keyid) : 0;
m.insert(keyid, age + 1); Other methods
// Get number of elements:
print m.elements();
// Get the used types:
print m.definitionString();
// Dump the whole map as a string:
print m.toString();

Image Class

Image is a system class for handling image data.
The class can construct and manipulate Image objects of the following file types:
Raster (bitmap) formats - .bmp, .gif, .jpg, .png, .tiff, and .exif
Vector formats - .emf and .wmf
Because of security reasons the class in Dynamics Ax can not be run from the server and is bound to the client.

The image object can be created from scratch, captured from the screen or loaded from a file. It can then be cropped, resized, flipped, rotated etc...


Examples
Load an image from file and place it in a table:

Image image;
str fileName;
;
filename = myImagePath + myImageFile;
if (Image::canLoad(Filename))
{
image.loadImage(Filename);
myTable.imageContainer = image.getData();
}...and to display the image on a form:

Image Image;
container imageContainer;
;
imageContainer = myTable.imageContainer;
Image = new Image();
Image.setData(imageContainer);
imageWindow.image(Image);
imageWindow.widthValue(image.width());
imageWindow.heightValue(image.height());

Array Class

Array (Foundation class) function the same way as ordinary arrays, the main difference being thata table buffers and objects can be used as contents.
The array is created with a type that defines what contents the array contains. It can not use Types::Anytype!
Contents
1 Example
2 Tips
3 Methods
4 See also
Example
array a = new array(Types::string);
a.value(1, "First");
a.value(2, "Second");
a.value(3, "Third");
print a.lastIndex();
print a.toString();
pause;
Tips
When inserting the array is dynamicly expanded. If you just want to add an element to the end of the array it is possible to use:

a.value(a.lastIndex()+1, "element");
If a container is needed use the pack method:
c = a.pack();

Methods
definitionString
exists
lastIndex
pack
toString
typeId

Validate field values on form

A nifty trick for comparing the "old" value against the "new" value using "obj.orig()" when validating a field on a form.
Example:
Change the behavior of the "Customer Group" on "CustTable" form.
Overwrite the method "validate" on the field "CustGroup" on the data source on "CustTable" public boolean validate()
{
boolean ret;

ret = super();

return ret;
}
Change method:

public boolean validate()
{
boolean ret;
;
if(custTable.orig().CustGroup == '30') // Non changeable group.
{
info('You cannot change this Customer group');
ret = false;
} else {
ret = super();
}
return ret;

}
Note: I've explained the general principle for validating a field against it's old value, however there are some issues with the data source not refreshing data set when changing the value back and forth.

How to count records in a Query

Query q;
QueryRun qr;
QueryBuildDataSource qbd;
;
q = new Query();
qbd = q.addDataSource(TableNum(CustTable));
qr = new QueryRun(q);
info(int2str(SysQuery::countTotal(qr)));
As seen you can use SysQuery object.
Runtime / Performance considerations
The call you are doing here works well if and only if you have exactly one datasource in the query. In the case you are starting to join up stuff here, things get complicated, as the implementation behind this call (specifically SysQuery::countPrim()) does actually loop over the entire resultset. It has to be taken with care therefore.
Retrieved from "http://www.axaptapedia.com/How_to_count_records_in_a_Query"

Create New AOT Project From Template

// Takes a Template Project Name and a Target Project Name
// and automatically creates a new Project from a Template Project.
//
static void CreateNewAOTProjectFromTemplate(Args _args)
{
ProjectNode templateproject;
ProjectNode pretargetproject;
ProjectNode targetproject;
TreeNode sharedProject;
UtilElements utilElement;
str templateprojectname;
str pretargetprojectname;
str targetprojectname = 'AATest';
str pretargetprojectname()
{
return pretargetprojectname;
}
;
// Get the templateprojectname from someplace where the name of a Template project can be found.
// NOTE: You'll need to change this Line and assign your own Template name.
templateprojectname = DPA_Parameters::find().AOTProjectTemplate;
sharedProject = SysTreeNode::getSharedProject();
if (sharedProject.AOTfindChild(targetprojectname))
return;
// Find the Template Project and load it for inspection
templateproject = sharedProject.AOTfindChild(templateprojectname);
if (!templateproject)
return;
templateproject.loadForInspection();
// Duplicate the Template Project using an intermediary ProjectNode
pretargetproject = templateproject.AOTDuplicate();
pretargetproject.AOTsave();
// Get the name of the duplicated project - CopyOf%1 (%1 = whatever the Template name is)
pretargetprojectname = pretargetproject.AOTname();
// Rename the duplicated Project to the desired project name.
// NOTE: I tried other ways, but this is the only way that worked!
// And, it's fast!
ttsbegin;
select firstonly forupdate utilElement
where utilElement.recordType == UtilElementType::SharedProject
&& utilElement.name == pretargetprojectname();
if (utilElement)
{
utilElement.name = targetprojectname;
utilElement.doUpdate();
}
ttscommit;
// Have to refresh the SharedProject Node - Very important!
sharedProject.AOTrefresh();
// Now just get the new project and open it.
targetproject = sharedProject.AOTfindChild(targetprojectname);
targetproject.AOTrun();
}

Friday, December 19, 2008

working on Bits

static void Bits(Args _args)

{

    int     mask;

    int setBit(int _bitMask,int _bit)

    {

        return _bitMask | (1 <<>

    }
    int killBit(int _bitMask,int _bit)

    {

        return _bitMask & ~(1 <<>

    }

    boolean bit(int _bitMask, int _bit)

    {

        return setBit(0,_bit) & _bitMask;

    }

    int flipBit(int _bitMask, int _bit)

    {

        return bit(_bitMask,_bit) ? killBit(_bitMask, _bit) : setBit(_bitMask, _bit);

    }

    int invertMask(int _bitMask)

    {

        return ~_bitmask;

    }

    int xorMask(int _bitMask1, int _bitMask2)

    {

        return _bitMask1 ^ _bitMask2;

    }

   str bitString(int _bitMask)

    {

        str bS;

        int i;



        for (i=31;i>=0;i--)

            bS += bit(_bitMask,i) ?  "1" : "0";

       return bS;

    }

    ;

    mask = setBit(mask,4);

    print bitString(mask);

   mask = flipBit(mask,7);

    print bitString(mask);

   mask = killBit(mask,7);

    print bitString(mask);
    mask = invertMask(mask);

    print bitString(mask);

    mask = xorMask(mask,255);

    print bitString(mask);

   pause;

}

importing chart of accounts from CSV file

static void CoAImport(Args _args)
{
LedgerTable ldt;
AsciiIO inFile;
Filename filename;
Container line;
str tmp1, tmp2, tmp3;
Dialog dialog;
DialogField dialogField;
;

/*
Note - In this example, I have used 'Dialog' & DialogField classes
as a tool for me to select the file that has to be imported. If required, you can directly select the file to be imported. For ex -
filename = 'C:\\';
*/

dialog = new Dialog("Import Chart of Accounts");
dialogfield = dialog.addField(typeid(Filenameopen), "File Name");
dialog.run();

if (dialog.run())
{
  filename = (dialogfield.value());
}

/*
Note - In this example, I have used AsciiIO class. But you can also use
CommaIO class as well. Basically all these classes (AsciiIO, CommaIO,
Comma7Io etc) derives from 'Io' base class. For more info, please
refer to Io class.
*/
inFile = new AsciiIO (filename, 'R');

if (!inFile || infile.status() != IO_Status::Ok )
{
   //strfmt - function for formatting text string
   throw error (strfmt("@SYS19312",filename));
}
ttsbegin;

infile.inRecordDelimiter('\n');
infile.inFieldDelimiter(',');

//Checking status of last operation..
while (infile.status() == IO_status::Ok)
{
   line = infile.read();

   if (line)
   {
     ldt.initValue();
     ldt.AccountNum = conpeek(line,1);
     ldt.AccountName = conpeek(line,2);
     ldt.AccountNameAlias = conpeek(line,3);
     ldt.AccountPlType = conpeek(line,4);
     ldt.doInsert();
   }
}
ttscommit;

/*
Some additional notes:

1) 'line' is a container. It is a general data type. Its purpose is very similar to temporary tables. However there are some subtle yet great difference between temp tables and containers. For more info, please refer to Developer's guide.

2) Conpeek - To return specific element from a container
*/
}

working on container

static void Container1(Args _args)

{

   container   con, conSet;

    Set        set       = new Set(Types::String);

    int          intValue  = 42;

    real        realValue = 1.61803;

    ;
    set.add("John");

    set.add("Paul");

    set.add("George");

    set.add("Ringo");

    print set.toString();
    con = [set.pack(),intValue,realValue];
    // do some processing, call methods using the container etc.

    [conSet,intValue,realValue] = con;

    set = Set::create(conSet);

    set.add("Yoko Ono");

    print set.toString();

    pause;

}

Active X create Analog Meter

static void createAnalogMeter(Args _args)
{
    Form                           formBrowse;
    FormActivexControl      activex;
    FormRun                      formRun;
    Args                            args;

    TreeNode                treeNode;
    Object  waitObject = new Object();
    ;

    formBrowse = new Form('AnalogMeter Example', true);

    formBrowse.design().width(500);
    formBrowse.design().height(500);
    formBrowse.design().caption('Analogmeter');
    formBrowse.design().addControl(FormControlType::ActiveX, 'Browser');

    args = new Args(formBrowse.name());
    args.name(formBrowse.name());
    args.object(formBrowse);

    formRun = classFactory.formRunClass(args);
    formRun.init();

    activex = formRun.design().controlName('Browser');
    activex.height(1,1);
    activex.width(1,1);
    activex.className('{706FF037-D46D-11D2-AB8D-0008C7631C69}');
    activex.BackColor(winapi::RGB2int(255,255,236));
    activex.displayHotArea(true);
    activex._Caption('Analog meter Example');
    activex.minValue(22000);
    activex.maxValue(40000);
    activex.addMarker(25000,255,"This is where i stand");
    activex.NeedleColor(winapi::RGB2int(0,128,0));
    activex.pos(25000);
    formRun.run();

    formRun.detach();

    while (activex.pos() != 40000)
    {

        waitObject.setTimeOut('Notify', 1000);
        waitObject.wait();
        activex.pos(activex.pos() + 1000);
    }

}

delete Files In Folder

void deleteFilesInFolder (filePath _inputPath)
 {
     FilePath folder = "E:\\My uninstaller" ;
     FilePath filePath;
     Filename    attachFilename;
     container fileInfo,file;
     int fileHandle;
     FileName fnameTmp;
;
//Browser

attachFilename = winapi::getOpenFileName(
            infolog.hWnd(),
            ["@SYS26054",'*.*'],
            '',
            "@SYS26798");
             if (! attachFilename)
        return;

file = Docu::splitFilename(attachFilename);

//Getting The Folder Path
folder = conpeek(file,3);

fnameTmp = folder;
filePath = fnameTmp;
//...
if (substr(fnameTmp, strlen(fnameTmp) -1,1) == '\\')
{
fnameTmp = fnameTmp + '*.*';
}
else
{
fnameTmp = fnameTmp + '\\*.*';
filePath = filePath + '\\';
}

fileInfo = winAPI::findFirstFile(fnameTmp) ;
fileHandle = conpeek(fileInfo, 1);

fnameTmp = winapi::findNextFile(fileHandle) ;

while (fnametmp)
{
fnameTmp = winapi::findNextFile(fileHandle) ;

if (fnameTmp != "")
winAPI::deleteFile( filePath + fnameTmp);

}
}

ExecuteCodeFromFile

static void ExecuteCodeFromFile(Args _args)

{

    #File

    AsciiIo     asciiIo = new AsciiIo("c:\\findCustomer.xpp",#io_read);

    XppCompiler xppCompiler = new XppCompiler();

    Source      source;

    str         line;

    CustTable   custTable;

    ;

    if (asciiIo)

    {

        asciiIo.inFieldDelimiter(#delimiterEnter);

        [line] = asciiIo.read();



        while (asciiIo.status() == IO_Status::Ok)

        {

            source += #delimiterEnter;

            source += line;

            [line]  = asciiIo.read();

        }



        if (!xppCompiler.compile(source))

            error (xppCompiler.errorText());

        custTable = runbuf(source,'4000');

        print CustTable.Name;

    }

    else

        print "Could not open file";



    pause;

}
/*
The external file c:\temp\findCustomer.xpp:

CustTable findCustomer(CustAccount _accountNum)

{

    return CustTable::find(_accountNum);

}
*/

FileOperations

static void FileOperations(Args _args)
{
CustTable cust;
CommaIO myfile;
TextBuffer tb;
counter recordCount;
int i;
str fileName;
;

fileName = "C:\\Axapta1.txt"; //<- Alternatively 'Dialog' can be used.
//<- Instead of '.Txt', extensions like
//.Doc, .Xls can also be used.

//Creating new file with write access
myfile = new commaIO(fileName,"w");


//Setting up Record delimiter
myfile.outRecordDelimiter("\r\n");
myfile.outFieldDelimiter(",");


while select cust order by accountnum
{
myfile.write(cust.AccountNum, cust.Name);
}

myfile.write('..........................................');


//To find out record count & write the same end of the file
recordcount = new sysdicttable(tablenum(custTable)).recordCount();
myfile.write("Inserted " + int2str(recordcount) + " Records");

//To close this file
myfile = null; //<- Finalize() method is protected. Hence this alternative ...

//To copy contents of this file into clipboard
tb = new TextBuffer();
tb.fromFile(filename);
tb.toClipboard();
tb = null;

//To delete this file
//winapi::deleteFile(fileName);


//Alternatively To move this file
//winapi::moveFile(fileName, newfileName); // <- Declare 'newfileName'
}



getTableProperty

static void getTableProperty(Args _args)
{
/*
    Author      :       Harish Mohanbabu
    Date        :       April 16, 2007
    Purpose     :       To obtain table properties
*/

    #AOT
    str             Property;
    TreeNode        TreeNode;
    identifiername  identifiername;
    str             Name;
    ;

    TreeNode    = TreeNode::findNode(#TablesPath);
    TreeNode    = TreeNode.AOTfirstChild();
    while (TreeNode)
        {
            Name = TreeNode.AOTname();
            Property       = TreeNode.AOTgetProperties();
            identifiername = findproperty(Property, 'SecurityKey');
            info (strfmt("%1, %2", Name, identifiername));
            treenode = TreeNode.AOTnextSibling();
        }
}

Set Examples

static void JobSetExamples(Args _args)

{

    custTable   custTable_1;

    custTable   custTable_2;

    Set         setCustTable_1;

    Set         setCustTable_2;

    Set         compare;



    set record2set(common _record)

    {

        DictTable       dictTable;

        DictField       dictField;

        FieldId         fieldId;

        Set             recordSet;

        ;



        dictTable = new DictTable(_record.tableId);

        recordSet = new Set(Types::String);



        for (fieldId = dictTable.fieldNext(0);fieldId;fieldId = dictTable.fieldNext(fieldId))

        {

            dictField = dictTable.fieldObject(fieldId);

            if (!dictField.isSystem() && dictField.arraySize() == 1)

                recordSet.add(strfmt("%1#%2",dictField.name(),_record.(fieldId)));

        }



        return recordSet;

    }

    ;



    custTable_1            = CustTable::find('4000');

    custTable_2            = CustTable::find('4000');

    custTable_1.AccountNum = "1234";

    custTable_1.Name       = "Test";

    custTable_2.CreditMax  = 333.33;



    setCustTable_1 = record2set(custTable_1);

    setCustTable_2 = record2set(custTable_2);



    compare = Set::difference(setCustTable_1,setCustTable_2);

    print compare.toString();



    compare = Set::difference(setCustTable_2,setCustTable_1);

    print compare.toString();



    compare = Set::intersection(setCustTable_1,setCustTable_2);

    print compare.toString();



    compare = Set::union(setCustTable_1,setCustTable_2);

    print compare.toString();



    pause;

}

realPrecision

static void realPrecision(Args _args)

{

    int     i,j;

    real    a,b;



    for (i=1;i<=3;i++)

    {

        a = exp10(i) - 1;

        b = exp10(i) - 1;

        for (j=1;j<=16;j++)

        {

             a += (9 / exp10(j));

             b += (9 / exp10(j));

             print strfmt("(%1) a     = %2", num2str(j,2,0,0,0),num2str(a,25,16,1,1));

             print strfmt("(%1) b     = %2", num2str(j,2,0,0,0),num2str(b,25,16,1,1));

             print strfmt("(%1) a + b = %2", num2str(j,2,0,0,0),num2str(a+b,25,16,1,1));



             if (j mod 4 == 0)

                pause;

        }

    }



}

setTableProperty

static void setTableProperty(Args _args)
{
/*
    Author     :       Harish Mohanbabu
    Date       :       April 16, 2007
    Purpose    :       To obtain table properties
*/

    str                Property;
    TreeNode    TreeNode;
    str               Name;
    ;

    Name = "CustTable";
    TreeNode  = TreeNode::findNode("Data Dictionary\\Tables\\" /*+ Table*/);
    if (TreeNode)
            {
           ttsbegin;
           Property       = TreeNode.AOTgetProperties();
           Property       = SetProperty(Property, "ModifiedDate", "Yes");
           TreeNode.AOTsetProperties(Property);
           TreeNode.AOTcompile();
           TreeNode.AOTsave();
        }
}

Test Query

static void Test_Query(Args _args)

{

    CustTable            custTable;

    Query                query      = new Query();

    QueryRun             qr         = new queryRun(query);

    QueryBuildDataSource qbds       = qr.query().addDataSource(tableNum(CustTable));

    QueryBuildRange      qbrAccN    = qbds.addRange(fieldNum(CustTable,AccountNum));

   // QueryBuildRange      qbrCountry = qbds.addRange(fieldNum(CustTable,Country));

    QueryBuildFieldList  qbfl       = qbds.fields();

    ;



    qbrAccN.value('4000..4050');

    qbrAccN.status(RangeStatus::Locked);

    //qbrCountry.value('CA..NO');



    qbfl.addField(fieldNum(CustTable,CreditMax),SelectionField::Sum);

    qbfl.addField(fieldnum(CustTable,RecId),SelectionField::Count);



//    qbds.addSortField(fieldnum(CustTable,Country));

    qbds.addSortField(fieldNum(CustTable,Currency));

    qbds.orderMode(OrderMode::GroupBy);



    if (qr.prompt())

    {





        while (qr.next())

        {

            custTable = qr.get(tableNum(CustTable));



            print strfmt("%1 %2 %3 (%4 records)",/*custTable.Country*,*/custTable.Currency,

                         num2str(custTable.CreditMax,10,2,0,0),custTable.RecId);

        }



    }



    pause;

}

TestDateTimeConversion

static void TestDateTimeConversion(Args _args)
{
    LoginProperty             loginProperty = new LoginProperty();
    ODBCConnection      con;
    Statement                    stmt;
    str                                  sqlString, result;
    ResultSet                     resultSet;
    ;

    loginProperty.setServer('LocalServer');
    loginProperty.setDatabase('AXDB');
    loginProperty.setUsername('bmssa');
    loginProperty.setPassword('bmssa_pwd');
    con = new ODBCConnection(loginProperty);
    sqlString =   "SELECT * FROM LedgerTrans WHERE DATAAREAID=' "
                         + curExt()
                         + "' "
                         + " AND TRANSDATE
                         + date2str(str2date('22/02/2006',123),321,2,3,2,3,4)
                         + " ' AS datetime) "
                         + " AND TRANSDATE> CAST(' "
                         + date2str(str2date('20/02/2006',123),321,2,3,2,3,4)
                         + " ' AS datetime) ";
    stmt = Con.createStatement();
    resultSet = Stmt.executeQuery(sqlString);
    resultSet.next();
    result = resultSet.getString(1);
    if (result)
    {
        info(result);
    }
    else
    {
        info( "No record! " );
    }

}

Method to calculate the date difference

intvNo(columDate, offsetDate, IntvScale::WeekDay)

Thursday, December 18, 2008

Using global search for virtual tables

The global search is a feature in Dynamics Ax 4.0 which allows you to search for a string in several tables (like a full text search). You setup some tables and fields to be searched in and start a data crawler that collects the data to be searched.

Now, if some of your tables that are searched belong to a virtual company, the data crawler will mark them as records of the company it is running in. That means that if you want to search for your virtual data from a different company, it will return no results.

Example: you have the companies xxx and yyy and a virtual company vir. Table CustTable is virtual (dataareaid of the records is vir). The data crawler runs in company xxx and will mark the CustTable records as belonging to company xxx .
Now, if you start a search in company yyy, it will not find the Custtable records as they seem to belong to a different company. You could only set up a second data crwaler for company yyy which would collect exactly the same records and you would need to store them twice in your database.

The following changes will circumvent that: you will be able to see data from different companies. There are some drawbacks, however: you will be able to see search results from your "data crawler company". But it the data is from a non-virtual table, you will not be able to see the results. But I hope it will lead you to a way where you can make your own modifications to get the best out of the global search. Remember: all changes you make are at your own risk.

Here are the changes you have to do:

Class SysGSSearchStart, method startSearch
comment the following line:
infolog.add(Exception::Warning,"@SYS98793");

With that, there will be no warning if you are working in a company where the data crawler is not running.

Class SysSearch, method search:

at line 28, just after "if (!searchname)" add:
select firstonly RecId from sysSearchName 
where sysSearchName.Design == 'SDS_xxx_default' 
&& sysSearchName.LanguageId == this.languageId();

if (!sysSearchName)


replace the xxx in the 'SDS_xxx_default' with the company id where the data crawler is running.

Class SysSearch, methods searchWord and searchExactWord:

at line 11, replace the "where sysSearchName.Design == this.design() &&" with:
where (sysSearchName.Design == this.design() 
sysSearchName.Design == 'SDS_xxx_default') &&

again, replace the xxx in the 'SDS_xxx_default' with the company id where the data crawler is running.

Class SysSearchDoDataSearch, method buildItemListXML:

at line 11, after a while select indextable from sysDataSearch block, add the following code:

changecompany('xxx')
{
sysDataSearch = null;
while select IndexTable from sysDataSearch
{
dictTable = new DictTable(sysDataSearch.IndexTable);
if (dictTable.rights() != AccessType::NoAccess)
searchTableMap.insert(dictTable.id(),0);
}
}
sysDataSearch = null;

Replace 'xxx' with the company id where the data crawler is running.

At line 77, after select sysDataSearch where sysDataSearch.SearchGroupId == m_eSearchGroupDef && sysDataSearch.IndexTable == tableid; add the following code:

if (!sysDataSearch)
{
changecompany('xxx')
{
sysDataSearch = null;
select sysDataSearch
where sysDataSearch.SearchGroupId == SearchGroupDef 
&& sysDataSearch.IndexTable == tableid; 
}
}

Replace 'xxx' with the company id where the data crawler is running.

Using global search for virtual tables

The global search is a feature in Dynamics Ax 4.0 which allows you to search for a string in several tables (like a full text search). You setup some tables and fields to be searched in and start a data crawler that collects the data to be searched.

Now, if some of your tables that are searched belong to a virtual company, the data crawler will mark them as records of the company it is running in. That means that if you want to search for your virtual data from a different company, it will return no results.

Example: you have the companies xxx and yyy and a virtual company vir. Table CustTable is virtual (dataareaid of the records is vir). The data crawler runs in company xxx and will mark the CustTable records as belonging to company xxx .
Now, if you start a search in company yyy, it will not find the Custtable records as they seem to belong to a different company. You could only set up a second data crwaler for company yyy which would collect exactly the same records and you would need to store them twice in your database.

The following changes will circumvent that: you will be able to see data from different companies. There are some drawbacks, however: you will be able to see search results from your "data crawler company". But it the data is from a non-virtual table, you will not be able to see the results. But I hope it will lead you to a way where you can make your own modifications to get the best out of the global search. Remember: all changes you make are at your own risk.

Here are the changes you have to do:

Class SysGSSearchStart, method startSearch
comment the following line:
infolog.add(Exception::Warning,"@SYS98793");

With that, there will be no warning if you are working in a company where the data crawler is not running.

Class SysSearch, method search:

at line 28, just after "if (!searchname)" add:
select firstonly RecId from sysSearchName 
where sysSearchName.Design == 'SDS_xxx_default' 
&& sysSearchName.LanguageId == this.languageId();

if (!sysSearchName)


replace the xxx in the 'SDS_xxx_default' with the company id where the data crawler is running.

Class SysSearch, methods searchWord and searchExactWord:

at line 11, replace the "where sysSearchName.Design == this.design() &&" with:
where (sysSearchName.Design == this.design() 
sysSearchName.Design == 'SDS_xxx_default') &&

again, replace the xxx in the 'SDS_xxx_default' with the company id where the data crawler is running.

Class SysSearchDoDataSearch, method buildItemListXML:

at line 11, after a while select indextable from sysDataSearch block, add the following code:

changecompany('xxx')
{
sysDataSearch = null;
while select IndexTable from sysDataSearch
{
dictTable = new DictTable(sysDataSearch.IndexTable);
if (dictTable.rights() != AccessType::NoAccess)
searchTableMap.insert(dictTable.id(),0);
}
}
sysDataSearch = null;

Replace 'xxx' with the company id where the data crawler is running.

At line 77, after select sysDataSearch where sysDataSearch.SearchGroupId == m_eSearchGroupDef && sysDataSearch.IndexTable == tableid; add the following code:

if (!sysDataSearch)
{
changecompany('xxx')
{
sysDataSearch = null;
select sysDataSearch
where sysDataSearch.SearchGroupId == SearchGroupDef 
&& sysDataSearch.IndexTable == tableid; 
}
}

Replace 'xxx' with the company id where the data crawler is running.

Send message to online user in Dynamics AX 4.0 (quick & dirty)

static void sendAlert(Args _args)
{
EventInbox inbox;
EventInboxId inboxId;

inboxId = EventInbox::nextEventId();

inbox.initValue();

inbox.ShowPopup = NoYes::Yes;
inbox.Subject = "Message to online user";
inbox.Message = "Message you want to send to user";
inbox.SendEmail = false;
inbox.UserId = curUserID();

inbox.InboxId = inboxId;

inbox.AlertCreatedDate = systemdateget();

inbox.AlertCreateTime = timeNow();

inbox.insert();

}

Be careful when resetting your usage data

First of all, my apologies for not having written anything in the last weeks. But I'm really busy with my project at the moment....

But here's another thing for you:
in Dynamics AX 4.0, a "Favorites" menu was introduced. You can drag a form to your favorites pane. Then you will be asked to give it a name, and: you can add a saved query to it. With that, you can easily make favorites the open the forms filtered on the data you just need.

But there's another thing: as long as your system is still in change, everybody recommends that you delete your usage data from time to time (in User/Options).

But what happens to your favorites if you delete all your usage data?
The answer is: you'll get an error message. The system will tell you that it cannot find your saved query anymore. So, the whole work saving your queries and making your favorites is lost.

A small little addition to the method "reset" in the form "SysLastValue" will help you avoid that.

Add the line

&& _sysLastValue.recordType != UtilElementType::UserSetupQuery;

in the "delete_from" statement.

So, the deletion will not delete your saved queries.
Things that could be improved:
  • Only skip queries that have been saved with a name
  • Only skip queries that are used for a favorite menu item

Automatically open grid filter when opening form

In some forms it may be convenient that the grid filter (new in Ax 4.0) is activated automatically. If you want to get that behaviour, just add one line in the form's run method, just after the super() call:

this.task(2855);

So, your run() method should look like:


public void run()


{


..... // some programming lines....
super();
this.task(2855);



}

Activating query tracing for all users

Sometimes when you are tracking performance (or other) problems, you may want to activate the long running query tracing for all users. Here is a little job that will do that. Beware: you have to activate trace settings on your AOS server, unless most of the queries will not be caught.


static void Set_SQLTrace_AllUsers(Args _args)
{
#LOCALMACRO.FLAG_SQLTrace (1 << 8) #ENDMACRO
#LOCALMACRO.FLAG_TraceInfoQueryTable (1 << 11) #ENDMACRO

boolean set;
UserInfo userInfo;
;

set = true;
ttsbegin;
while select forupdate userinfo
{
userinfo.querytimeLimit = 1000;
if (set)
{
userInfo.DebugInfo = userInfo.DebugInfo | #FLAG_SQLTrace;
userInfo.TraceInfo = userInfo.TraceInfo | #FLAG_TraceInfoQueryTable;
}
else
{
userInfo.DebugInfo = userInfo.DebugInfo ^ #FLAG_SQLTrace;
userInfo.TraceInfo = userInfo.TraceInfo ^ #FLAG_TraceInfoQueryTable;
}

userinfo.update();
}
ttscommit;
}

Open web pages from X++ code

infoLog.urlLookup('http://nameofthepageyouwanttoopen.com');

AD user creation

using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;

namespace Microsoft.Dynamics.CreateADUser
{
    public class NewUser
    {
        string defaultNC;
        string alias;
        string fullName;
        string password;
        string ou;

        public void setDomain(string _defaultNC)
        {
            defaultNC = "DC=" + _defaultNC;
        }
        public void setAlias(string _alias)
        {
            alias = _alias;
        }
        public void setFullName(string _fullName)
        {
            fullName = _fullName;
        }
        public void setPassword(string _password)
        {
            password = _password;
        }
        public void setOu(string _ou)
        {
            ou = _ou;
        }

        public string execute()
        {
            DirectoryEntry container, user;
            string ret;

            try
            {
                //This creates the new user in the "users" container.
                //Set the sAMAccountName and the password
                container = new DirectoryEntry("LDAP://OU=" + ou + ", " + 
defaultNC + ",DC=NO");
                user = container.Children.Add("cn=" + fullName, "user");
                user.Properties["sAMAccountName"].Add(alias);
                user.CommitChanges();
                user.Invoke("SetPassword", new object[] { password });

                //This enables the new user.
                user.Properties["userAccountControl"].Value = 0x200; 
//ADS_UF_NORMAL_ACCOUNT
                user.CommitChanges();
                ret = "OK";
            }
            catch (Exception e)
            {
                ret = e.ToString();
            }
            return ret;
        }
    }
}

Just compile and put the dll-file in the client\bin folder of Ax and create 
a reference from the aot and use the class like this in Ax:

------------------------------------
static void Job1(Args _args)
{
    Microsoft.Dynamics.CreateADUser.NewUser user;
    ;
    user = new Microsoft.Dynamics.CreateADUser.NewUser();
    user.setDomain(/*yourdomain*/);
    user.setAlias(/*alias of the user you want to create*/);
    user.setFullName(/*Full name of the user*/);
    user.setPassword(/*Password*/);
    user.execute();
}

Basics of changecompany

The command changecompany is used to switch between companies in you Dynamics Ax environment through X++ code. This is the basics:
  // We are now in company FO
  changecompany("FO1")
  {
      // We are now in company FO1.
  }
  // Back in company FO
Let’s say that we start out in company FO and want to copy some data from company FO to company FO1. This is one way to do it.

 static void FO_changecompany(Args _args)
 {
    FO_TestTable    table, table2;
    ;
 
    while select table
    {
        changecompany("FO1")
        {
            table2 = null;
 
            table2.ttsbegin();
            table2.Id           = table.Id;
            table2.Name         = table.Name;
            table2.coolOrNot    = table.coolOrNot;
            table2.insert();
            table2.ttscommit();
        }
    }
 }

When trying to figure this out I first used this command to set data from table to table2:
 table2.data(table.data());
This didn’t work at all, all you got was two new records in company FO. My guess is that this was because all data including dataAreaId was copied and inserted when using table2.data(). This means that it doesn’t matter which company you are in when doing the insert, because dataAreaId always is set to FO when copying records from FO this way.
If you set the fields induvidually, dataAreaId is left blank and is only set when the insertion of data is done to the database. When this is done, the value that is assigned is based on what company you are currently in, and in our job FO_changecompany this means FO1.


Browse the data dictionary with X++

Browsing the Data dictionary through X++ code is easier than you may think. Let’s say you want to list all of the fields in table InventTable.
This is one way to do it:

  static void FO_dataDictionary(Args _args)
  {
      Dictionary      dictionary = new Dictionary();
      DictTable       table;
      DictField       field;
      InventTable     inventTable;
      int             j;
      ;
 
      table = dictionary.tableObject(inventTable.TableId);
 
      print table.id();
      print table.name();
      pause;
 
      for(j=1; j <= table.fieldCnt(); j++)
      {
          field = new DictField(table.id(), 
                                table.fieldCnt2Id(j));
          print field.name();
      }
 
      pause;
  }

Using this job we list all of the fields in InventTable, even the system fields such as dataAreaId and createdDate. If we want to exclude the system fields we add an If – statement that checks if the fields are system fields using the method isSystem():
 static void FO_dataDictionary(Args _args)
  {
      Dictionary      dictionary = new Dictionary();
      DictTable       table;
      DictField       field;
      InventTable     inventTable;
      int             j;
      ;
 
      table = dictionary.tableObject(inventTable.TableId);
 
      print table.id();
      print table.name();
      pause;
 
      for(j=1; j <= table.fieldCnt(); j++)
      {
          field = new DictField(table.id(), 
                                table.fieldCnt2Id(j));
 
          if(!field.isSystem())
          {
              print field.name();
          }
 
      }
 
      pause;
  }



Caching in Microsoft Axapta

The record cache in Microsoft Axapta is a performance-enhancing feature, aimed at avoiding unnecessary database access when reading the same record repeatedly. 
Record caching is only possible when the table containing the records is static. Otherwise, it is necessary to read the current record from the database when creating a relation to that particular record. This will ensure data integrity.
The enabling of caching on a table is dependent on the existence of a unique index. The caching key is determined either by the primaryIndex in the table, if one exists, or the first unique index. 
Note: 
Caching is only active for selects done with predicates on the whole caching key.
Only selects that retrieve all fields in the record are placed in the cache. Ranges are not cached.
There are 4 types of caching:
None: caching is disabled.
NotInTts: all key selects are cached. When in tts, the record is read once. The record is select-locked when read in tts, which ensures that the record cached is not updated while the tts is active.
Found: all successful key selects are cached. All caching key selects are returned from caching if the record exists there. A select-for-update in tts will force reading from the database, as well as update the cache.
FoundAndEmpty: all selects on caching keys are cached, even selects not returning data. All caching key selects are returned from caching if the record exists there, or the record is marked as non-existing in the cache. A select-for-update in tts will force reading from the database as well as update the cache.
Tip: 
Found caching is appropriate for tables that are static (or only receive inserts). FoundAndEmpty caching is appropriate for completely static tables.
The application code must be aware that cached data has no time-out. If the application is not specifically designed to handle updates to cached tables, data corruption will occur. Currently, the only way to refresh all types of caches, in both Two- and Three-tier solutions, is to restart Microsoft Axapta.
As long as all keys used from a table are verified within a tts, updating a table that is notInTts cached, does not jeopardize data integrity in the same way.
Only when Microsoft Axapta is performing an update or delete, is the cache synchronised with the table in the database. The synchronisation is not done for inserts. This means that any record, not found on a table with foundAndEmpty caching, will continue to be reported as not found, even after it has been created.
The behaviour on Microsoft Axapta Object Server thin clients is a little different if the Microsoft Axapta Object Server has exclusive use of that database. In this case the AOS will ensure that no invalid data can be read from cache. However, the problem with regards to inserts in a table with foundAndEmpty caching remains.
Methods to refresh caching
In addition to the above-described methods, there is also the “flush table name” X++ construction. This code will flush the table corresponding to the table-name from the cache. When in an AOS environment, the flush will only apply to the cache that resides where the code is run. Thus a flush run on the client will only flush the local cache, not the caching on the server.
Tip:
A delete_from  X++ construct will flush all caching of that particular table. 
The deleteCompany method on the application object will flush all tables from all caching.

Class InventOnhand

The class inventOnhand is very useful when retrieving inventory information for a specific item.
If we want to get the sum of an item for all warehouses and all configurations:
  static void FO_InventOnhand(Args _args)
  {
      InventOnhand        onhand;
      ;
 
      onhand = InventOnhand::newItemId("0001");
 
      // Physical inventory
      print onhand.physicalInvent();
 
      // Physical reserved
      print onhand.reservPhysical();
 
      // Total available
      print onhand.availOrdered();
 
      // Ordered reserved
      print onhand.reservOrdered();
 
      // Available physical
      print onhand.availPhysical();
 
      pause;
  }

Maybe we want the sum of an item in a certain warehouse, in our case the warehouse “GW”:
  static void FO_InventOnhand(Args _args)
  {
      InventOnhand        onhand;
      ;
 
      onhand = InventOnhand::newItemId("0001");
      onhand.parmInventLocationId("GW");
 
      // Physical inventory
      print onhand.physicalInvent();
 
      // Physical reserved
      print onhand.reservPhysical();
 
      // Total available
      print onhand.availOrdered();
 
      // Ordered reserved
      print onhand.reservOrdered();
 
      // Available physical
      print onhand.availPhysical();
 
      pause;
  }

If the item has different configurations, let’s say that item “0001” is a lamp that comes in the different colors red, green and black. But we only want to see the sum of the black(Has the configId “Black”) lamps:
  static void FO_InventOnhand(Args _args)
  {
      InventOnhand        onhand;
      ;
 
      onhand = InventOnhand::newItemId("0001");
      onhand.parmInventLocationId("GW");
      onhand.parmConfigId("Black");
 
      // Physical inventory
      print onhand.physicalInvent();
 
      // Physical reserved
      print onhand.reservPhysical();
 
      // Total available
      print onhand.availOrdered();
 
      // Ordered reserved
      print onhand.reservOrdered();
 
      // Available physical
      print onhand.availPhysical();
 
      pause;
  }

If we have some SalesLine data we can narrow this calculations even further, this is because in the SalesLine we have access to the InventDimId. With the InventDimId we can get the specific inventory data for this specific salesLine. This can look something like this if the method is created on the salesLine table:
inventDimParm.initDimActive(InventTable::find(this.ItemId)
                            .DimGroupId);
onHand = InventOnHand::newItemDim(this.ItemId, 
                      inventDim::find(this.InventDimId), 
                      inventDimParm);
The static method newItemDim() does just what we have done before with the parameters methods, but behind the scenes in the InventOnhand class. This is what happens:
  static InventOnhand newItemDim( 
                    ItemId             _itemId,
                    InventDim          _inventDim,
                    InventDimParm      _inventDimParm
                    )
  {
      InventOnhand inventOnhand = new InventOnhand();
      ;
 
      inventOnhand.parmInventDim(_inventDim);
      inventOnhand.parmInventDimParm(_inventDimParm);
      inventOnhand.parmItemId(_itemId);
 
      return inventOnhand;
  }

Depending on the source operating system to execute mode

Such as from Visual Studio can be known in many IDE's mode of execution of the source code and determines to be changed. 
Examples include fashion debug or release fashion. 
If the source code on the execution mode is queried, it may, for example, in the source code debug expanded fashion with a program to be compiled, which, for example, different types of logging implemented to improve problem analysis to be carried out. 
This can also make Microsoft Dynamics AX. 

The first must create a new macro level (in this example with the name Running Mode), which acts as a global switch between the execution Feeding types used: 
/* Macro for setting the running Mode. */
/* Activate for debug mode*/
#define .Debug(' true ') 
/* Activate for production mode. */
//#define.Debug('') 
Now, anywhere in the # debug code used to refer to the execution mode to consider: 
static void TestTheRunningMode(Args _args) 
#RunningMode # Running Fashion 
print "of the code should always be executed"; 
if (#Debug) 
print "code only in debug mode is running"; 
}  
pause; 
break; 
}  
Depending on how the Macro Running # debug mode has been defined, can now be activated any source (executed) or disabled (not) be. 

Get label string in different languages

A while ago I had the need to translate labels, I was creating eMail bodys while using SysMailer and wanted to use different languages for different customers.
I found the class SysLabel and tried to use it. But the problem was that this didn´t work. I allways got the string in the language that was set up under Tools/Options.
   print SysLabel::labelId2String2("@SYS54971", "en-us");
I usually have Swedish setup as language when I run Dynamics AX, and this is the result when running previous code.
Aktiveringsdatum
If there are any english readers: “Aktiveringsdatum” is the Swedish translation of “Activation date“.
I traced the code and found out that the label converts to it’s string before it´s sent into the method labelId2String2. Since the input value wasn’t a labelId anymore, the method just returned the exact string that was sent in.
I found a solution to this problem by piecing together the labelname in the call to the method. It seems that the label didn’t convert to it’s string when I did it this way.

   str     labelStringSwedish;
   str     labelStringEngUS;
   ;
 
   labelStringSwedish =
   SysLabel::labelId2String2("@"+"SYS54971", "en-us");
 
   labelStringEngUS   =
   SysLabel::labelId2String2("@"+"SYS54971", "sv");
 
   print labelStringSwedish;
   print labelStringEngUS;
   pause;

Result:
Aktiveringsdatum
Activation date
Some of you might have noticed that I use the method labelId2String2, therefore there should also exist a method named labelId2String. This is true, the difference between this two methods is not a big one.
·  labelId2String - Returns an empty string if an invalid labelId is used in the method.
·  labelId2String2 - Returns the string that was sent into the method if it isn’t a valid labelId.
If a valid labelId is sent into either of these two methods, the label string is returned.

Author: Erik Paulsson
Tags: label, labelId2String, labels, language, languages, SysLabel
This entry was posted on Thursday, November 1st, 2007 at 14:42 and is filed underDevelopment, Dynamics AX, Dynamics AX 4.0. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

How to create a new EventType

When using notifications in Dynamics AX we get different event types when setting up a new notification rule for different fields. If we try to create a notification rule for a date field, we get event types like “is due:”, “is due in:” and “has changed:”. If we create one for a string we get “has changed:” and “is set to:”.
Let’s say we want to create a new event type for fields containing numbers that checks if the number has doubled. This is how we do it.
We first need to create a new class that extends the EventTypeCUD class. We call our new class EventTypeCUDDoubleOrMore.

Classdeclaration extends EventTypeCUD:
class EventTypeCUDDoubleOrMore extends EventTypeCUD
 {
 }

We need a description that tells the user what the event type means. This is what the user will see then choosing appropriate event type.
1
2
3
4 public EventTypeDescription description()
 {
     return "has doubled:";
 }

We need to specify what type of fields our new event type will be applied to:
 public boolean isValidEventType(Types _types)
 {;
        switch(_types)
        {
            case    Types::Integer:
            case    Types::Int64:
            case    Types::Real:
                return true;
            default:
                return false;
        }
 
        return false;
 }

We also need a condition that will trigger a notification if our event is used:
1
2
3
4 public boolean isMatchingRule()
 {
     return (currentValue >= (originalValue * 2));
 }

And last but not least we need to specify that this event has the trigger FieldChanged.
public anytype parmTypeTrigger(EventTypeTrigger _typeTrigger
                                   = typeTrigger)
 {
     ;
     typeTrigger = _typeTrigger;
     if (typeTrigger != EventTypeTrigger::FieldChanged)
         throw error("@SYS96349");
     return typeTrigger;
 }

More info on how to create a new event type class can be found in Dynamics AX developer help chapter: Implement a New EventType Class.
What they don’t mention in the developer help is how to activate your new class. This is done in the class EventType method eventTypeClassIdsHook(). There are comments in the code that helps, we need to edit this method so it looks like this:
protected static container eventTypeClassIdsHook()
 {
     //   return connull();
     return [classNum(EventTypeCUDDoubleOrMore)]; 
     //    return [classNum(myEventTypeClass1), 
     //    classNum(myEventTypeClass2), ... ];
 }

Intercompany with buf2buf

Intercompany with buf2buf
When discussing the “Basics of changecompany” we copied table records from one company to another through X++ code. Using buf2buf makes this is a lot easier.
Buf2buf is used to copy one recordbuffer to another:
       buf2buf(Common _from, Common _to)
If we apply this to the code we used in “Basics of changecompany” we get:
 static void FO_changecompany(Args _args)
  {
      FO_TestTable    table, table2;
      ;
 
      while select table
      {
 
          changecompany("FO1")
          {
              table2 = null;
 
              ttsbegin;
 
              buf2buf(table, table2);
              table2.insert();
 
              ttscommit;
          }
      }
  }




Packing Slip posting

The Book of orders in Microsoft Dynamics AX is done about the class "Sales form letter or a specified their (secondary) classes. Each (confirmation, delivery, billing) is through a separate class, which from the base class "Sales form letter" is derived (see illustration). 
. To a job as a program code book, an object of class "Sales form letter" to be created. 
salesFormLetter = SalesFormLetter::construct(DocumentStatus::Confirmation);
 
. This happens as general in Microsoft Dynamics AX usual, the "construct" method of the class. As parameter of this method has the desired type of booking (eg confirmation, delivery, billing party). The "construct" method creates a Buchungsart the corresponding object and gives them back (in this case is a "SalesFormLetter_Confirm" object). 
The actual reservation is about the method "update" called.. Since this method for posting all the necessary data as a parameter can be passed, is a single assignment of the contract, for example, which should be posted, not necessary. 
Hierzu ein Beispiel: Here is an example: 
// --- --- Book without expression
static void PostingConfimation(Args _args) 
{
SalesFormLetter salesFormLetter;
SalesTable salesTable; 
SalesId salesId; 
PrintJobSettings printJobSettings; 
;
// Specify the contract, which should be posted.
salesId = "00423_036" ; 
salesTable = SalesTable::find(salesId); 

// Determine the type indicated by posting the Document Status
salesFormLetter = SalesFormLetter::construct(DocumentStatus::Confirmation); 

// Book of the contract (but not printing).
salesFormLetter.update(salesTable, 
SystemDateGet(),
SalesUpdate::All, 
AccountOrder::None, 
NoYes::No,
NoYes::No);
}In this example is good to see that for posting a mandate essentially only two steps are necessary. 
On the method "construct" an opponent Buchungstyp the object. 
By calling the method "update" the order book. 
Of course, even larger reservation or something more specific scenarios with "Class sales form letter" will be shown. So it is possible for example, just off the reservation respective print documents (once, repeatedly and in different formats), the mask for the reservation to open (so the user influence on the reservation can take) or the booking is not directly run, but this for the batch ready to make. 
So it is not too complex, just another example, to print and simultaneous posting of similar documents. 
// --- --- Book with expression
static void PostingConfimation_new(Args _args)
{
SalesFormLetter salesFormLetter;
SalesTable salesTable;
SalesId salesId;
PrintJobSettings printJobSettings;
;
// Specify the contract, which should be posted.
salesId = "00036_036" ;
salesTable = SalesTable::find(salesId);
salesFormLetter = SalesFormLetter::construct(DocumentStatus::Confirmation);
// Book of the contract and print (print medium Setting hr).
salesFormLetter.update(salesTable,
SystemDateGet(),
SalesUpdate::All,
AccountOrder::None,
NoYes::No,
NoYes::Yes);

// 2nd expression.
printJobSettings = new PrintJobSettings(salesFormLetter.printerSettingsFormletter(
PrintSetupOriginalCopy::Original));
// Where we want to print (here) file.
printJobSettings.setTarget(PrintMedium::File);

// In what format should be printed (PDF here).
printJobSettings.format(PrintFormat::PDF);
printJobSettings.fileName( @"C:\Test_Order.pdf" );

// Transfer of printing options to the sales letter form object.
salesFormLetter.updatePrinterSettingsFormLetter(printJobSettings.packPrintJobSettings());

salesFormLetter.printJournal();
}

Berechtigungen eines Benutzer mit X++ Code abfragen Permissions a user with X + + Code

. Microsoft Dynamics AX, all user permissions on SecurityKeys controlled. SecurityKeys may Forms, Form Controls, Tables, Table Fields, MenuItems, etc. in their properties will be deposited. 
Classes for this is through a MenuItems and the implementation of the method "static void Main (Args _ARG)" also possible, but it may be for a single method SecurityKey not be awarded. 
However, there are situations where the code execution, depending on the specific authorization of the user, should be / needs. In such a case, the X + + code, a review of the privileges of the user are carried out. 

If you want to check whether a user access to a SecurityKey, can this with the method hasSecurityAccess done. 

if ( hasSecurityKeyAccess(securitykeyNum(CustSetup), AccessType::View) ) 
 ( 
// Execute code, if appropriate authorization exists. 
 ) 
If you want to check whether a user access to a table we go with this method hasTableAccess. 

if ( hasTableAccess(tablenum(CustTable), AccessType::Edit) ) 
 { 
// Execute code, if appropriate authorization exists. 
}  
Not only must the table, but a single box to be checked can be changed using the method hasFieldAccess made. 

if ( hasFieldAccess(tablenum(CustTable), fieldnum(CustTable, AccountNum), AccessType::Delete) ) 
{
// Execute code, if appropriate authorization exists. 
}  
AccessType determines the parameters on which this permission each element is gepĆ¼rft (No access, view, edit, create, complete control). 
All methods (hasSecurityAccess, hasTableAccess, hasFieldAccess) are global methods, in the global class is defined.. Thus, these methods at any point in the source code can be used. It does not matter whether it is a form of the method, DataSource, class or table. 

Query or Not

I wanted here, nor the extended possibilities for the restrictions to give the best, but you can have everything ready for Axaptapedia read. 

 Therefore, initially only one thing I still would like to describe: 
Would be a restriction on an array of field (the dimension department, cost center, costs are a good example) can define these as follows created. 

queryRange = queryDS.addRange(fieldid2ext(fieldnum(InventTable, Dimension), 1
The draw of the range remains basically the same, only now using fieldid2ext nor the array index is specified. The index starts in Microsoft Dynamics Ax always on 1 

Now that this thing here is no longer treated, I would like a completely different way show records from the database in order to bring this example, in display screens. 

That will be a bit Tricky ... 
I begin today, but only "just" at. 

First we build us a new form and name this ArtikelSQL (The name is here eigendlich regardless, I have but for a mask decided article). 
Then we Invent the table using drag and drop a DataSource establish. At the conclusion, we show a few box (item number and article name ranges from first) on a newly added grid in this mask on. Ready - when calling the mask we get all the records in the table are Invent table. 

As a next step we define a variable in the Classdeclaration 

Source SQLAnweisung;
Before that, however, is made must be next, nor the method executeQuery on the DataSource overwritten. 
void executeQuery() {if (SQLAnweisung ( 
runbuf(SQLAnweisung, this .cursor());
else ( 
super();) 
) }

New is the IF query that checks whether a SQLAnweisung was created, or whether the variable SQLAnweisung is not empty. SQLAnweisung was not taken, the default query on the DataSource executed. Was but a SQLAnweisung is taken now instead of the query executed. 

Last but not least is missing only the SQLAnweisung, which must be created. 

void initSQLAnweisung()
{
XppCompiler compiler = new XppCompiler(); 
DictTable dictTable= new DictTable(Tablenum(InventTAble)); 
str SELECTAnweisung = 'SELECT * FROM '+dictTable.name();

SELECTAnweisung = ' void SQLSTMT('+dictTable.name() + ' '+dictTable.name() + ')\n{\n'+SELECTAnweisung+';\n}\n';

if (!compiler.compile(SELECTAnweisung))
{
setprefix( "@SYS57538" );
info (SELECTAnweisung);
error (compiler.errorText()); 
SELECTAnweisung = '';
}
}  This method creates the SQL statementIt is not enough simply a SELECT statement to write, because the compiler does not understand. Instead of a single SELECT statement writes simply a way around it, then the runbuf running. 
The SELECT statement is as follows and is equal to the variable SELECT statement assigned. 

SELECT * FROM InventTable 

This simple select statement we get all the records from the table Invent Table. At first glance, it is not easy to recognize but 

'Void SQLSTMT (' dictTable.name + () + '' + dictTable.name () + ') \ n (\ n' SELECT statement + + '; \ n) \ n';

 In view of readable

void SQLSTMT(InventTable InventTable) 
{
SELECT * FROM InventTable;
}
and global variables SQLAnweisung assigned. 
Only to be sent securely if everything were correct and contain no compilation, this statement yet about the compiler and compiled. 

compiler.compile(SQLAnweisung) 
No errors were found Primavera is everything, otherwise the variable yet cleared to run when the method executeQuery no mistakes to get. The output of the error by the compiler is optional. 

The call the method "initSQLAnweisung" method is still the super () in the init method of the mask. 
Through the SQL statement has the advantage in a very simple way and thus very complex queries design. There are some disadvantages also, unfortunately, the standard functions such as filtering, sorting or grading work in the above example, not more. 
Wie immer wurde auch dieses Beispiel in Microsoft Dynamics Ax 4.0 erstellt. As always this example was also in Microsoft Dynamics Ax 4.0. 

How to identify the user that was used to change an object from AOT in AX2012

Get the object name for which we need to track these (user and date&time) information's. Login to SQL Server Management Studio an...