Monday, December 29, 2014

Getting number of recrods present in a query

If you want to get the total number of records from a query run object, which can be used to set total count in a progress bar (progress indication framework).

Solution: Use SysQuery::countloop([QueryRun _queryRun]). this will return the total number of records in a query object

Wednesday, December 24, 2014

Generating SSRS report in PDF via code X++

Following is a sample code for generating SSRS report in PDF format using X++ code. This sample code generates Purchase order confirmation report in PDF format and saves it into specified location.


PSAPurchaseOrderController poConfirmationController;
PurchPurchaseOrderContract purchPOContract;
SRSReportExecutionInfo     reportExecutionInfo;
Args                       args = new Args();
SrsReportRunImpl           srsReportRun;
VendPurchOrderJour         vendPurchOrderJour;
ReportName                 reportName = ssrsReportStr(PSAPurchaseOrder, Report);

str                        fileName;
;

select firstOnly vendPurchOrderJour
order by RecId desc
where vendPurchOrderJour.PurchId == '';//Replace with PurchOrder Id

if (!vendPurchOrderJour)
return '';


args.record(vendPurchOrderJour);
poConfirmationController= PSAPurchaseOrderController::construct();
poConfirmationController.parmArgs(args);
poConfirmationController.parmReportName(reportName);
reportExecutionInfo = poConfirmationController.parmReportContract().parmReportExecutionInfo();
purchPOContract= poConfirmationController.parmReportContract().parmRdpContract();


purchPOContract.parmRecordId(vendPurchOrderJour.RecId); // Record id must be passed otherwise the report will be empty

poConfirmationController.parmArgs(args);srsReportRun = poConfirmationController.parmReportRun() as SrsReportRunImpl;


fileName = System.IO.Path::Combine(WinAPIServer::getTempPath(), strFmt('purchConfirmation_%1.pdf', _purchId));
poConfirmationController.parmReportRun(srsReportRun);
poConfirmationController.parmReportContract().parmPrintSettings().printMediumType(SRSPrintMediumType::File);
poConfirmationController.parmReportContract().parmPrintSettings().overwriteFile(true);


poConfirmationController.parmReportContract().parmPrintSettings().fileFormat(SRSReportFileFormat::PDF);
poConfirmationController.parmReportContract().parmPrintSettings().fileName(fileName);

poConfirmationController.runReport();

Tuesday, November 25, 2014

Creating a class object using X++

The following Job shows the code used for creating a "Class" using X++ code. This code may be used in scenarios like AIF service, Workflow generation etc processes where different objects like "Class" are created automatically using the similar code mentioned below.

static void BlogTestJob(Args _args)
{
    ClassBuild  classBuild;
    str         header;
    str         newClassName = "SampleClass";
    ;

    header = 'public class '+ newClassName;
    classBuild = new ClassBuild(newClassName);
    classBuild.addMethod('classdeclaration', header + '\n{\n}\n');
    classBuild.addMethod('main', 'public static void main(Args    _args)' + '\n{\n}\n');
    classBuild.classNode().AOTcompile(1);

    info('Job completed....');
}

Monday, November 24, 2014

Importing Sales order XML using X++

The following job will create Sales order using Document service through X++ code.

static void JobTestWebService_ImportXml(Args _args)
{
    //feed the xml that was modified after exporting through exportxml job
    XmlDocument xml = XmlDocument::newFile(@'C:\SORequest.xml');
    AxdSalesOrder salesOrder;

    try
    {
        salesOrder = new AxdSalesOrder();
        salesOrder.create(xml.xml(),  new AifEndPointActionPolicyInfo(), new AifConstraintList());
    }
    catch
    {
        throw error('Error in document service');
    }
    info(strFmt('Sales order created'));
}

Sample XML File

<?xml version="1.0" encoding="utf-8" ?>
<SalesOrder xmlns="http://schemas.microsoft.com/dynamics/2008/01/documents/SalesOrder">
<SalesTable class="entity">
  <CustAccount>1204</CustAccount>
  <DeliveryDate>2011-10-11</DeliveryDate>
  <ReceiptDateRequested>2011-11-11</ReceiptDateRequested>
  <PurchOrderFormNum>ABC-98654</PurchOrderFormNum>
<TableDlvAddr class="entity">
<Address>KEVIN DAVIS 13075 MANCHESTER RD STE 228.A  DES PERES, MO 631311878 </Address>
<City>DES PERES</City>
<CountryRegionId>USA</CountryRegionId>
<County>JEFFERSON</County>
<ISOcode>US</ISOcode>
<LocationName>KEVIN DAVIS</LocationName>
<State>MO</State>
<Street>13075 MANCHESTER RD STE 228.A </Street>
<ValidFrom>2014-11-11T00:00:00Z</ValidFrom>
<ValidTo>2014-11-11T00:00:00Z</ValidTo>
<ZipCode>631311878</ZipCode>
</TableDlvAddr>
  <SalesLine class="entity">
<ItemId>0001</ItemId>
<SalesQty>20</SalesQty>
<SalesPrice>82.25</SalesPrice>
<SalesUnit>Pcs</SalesUnit>
  </SalesLine>
</SalesTable>

</SalesOrder>

Exporting XML Sales order using AIF service

The following job will export the Sales order information into an XML format which can be used by document service in order to test Sales order creation using XML.

static void JobTestWebService_ExportXml(Args _args)
{
    AxdAddress address;
    AxdSalesOrder salesOrder;

    AifEntityKey    key;
    Map             map;

    //dummy variable
    AifPropertyBag bag;

    map = new Map(Types::Integer, Types::Container);
    map.insert(fieldnum(SalesTable, SalesId), ['SO-101327']);
    
    key = new AifEntityKey();
    key.parmTableId(tablenum(SalesTable));
    
    key.parmKeyDataMap(map);

    try
    {
        address = new AxdAddress();
        salesOrder = new AxdSalesOrder();
        info('XML output');
        info(salesOrder.read(key, null, new AifEndPointActionPolicyInfo(), new AifConstraintList(), bag));
    }
    catch
    {
        throw error('Error in document service outbound');
    }
}

Filter records on form using X++

This post will shows the steps required to filter records on form using parameters provided by the caller.

Step 1
Declare a class variable 
In the ClassDeclaration method of form declare a QueryBuildRange

class FormRun extends ObjectRun
{
    QueryBuildRange      abcQueryRange;
    ABC                  abcCode;
}

Step 2
Initialize this range in the init method of datasource after super() call.

public void init()
{
    super();
    abcQueryRange = this.query().dataSourceName('X').addRange(fieldnum(X, ABC));

}

Step 3
Now assign the filter value in the executeQuery method of same datasource before super() call.

public void executeQuery()
{
    if (abcCode)
    {
        abcQueryRange.value(queryValue(abcCode));
    }
    super();

}

The query filtration part is done, you now need to fetch the value of "abcCode" from the caller. For this you need to grab the values from argument passed by the caller in the "init" method form.


public void init()
{
    if (element.args().record())
    {
        if (element.args().record().TableId == tablenum(x))
        {
            abcCode
 = X::findRecId(element.args().record().RecId).abcCode;
        }
    }
    super();

}

Wednesday, October 29, 2014

Fetching Financial Dimension Value X++

This post shows the code for extracting Financial Dimension value by providing dimension name for each dimension combination which is created and persisted. 

The DimensionAttributeValueCombination table contains information about accounts and various dimensions combinations that are used. However, they are stored as a combination ex: (100010-AX-00001- – – -). These values are retrieved through DimensionAttributeValueSetStorage storage class. This class can be used to manipulate these combinations.

The job below helps you in finding out the required values. 


static void AI_GetProjectCostCenterValue(Args _args)
{
    ProjTable                           projTable;
    DimensionAttributeValueSetStorage   dimStorage;
    str                                 dimValue;
       
    projTable = projTable::find('000409');
    dimStorage = DimensionAttributeValueSetStorage::find
                 projTable.DefaultDimension);
    
    info(strFmt('Project 000409, Cost Center value : %1',                        dimStorage.getDisplayValueByDimensionAttribute(                      DimensionAttribute::findByName('CostCenter').RecId)));
    
}

Output

Project 
In order to fetch Value and Description use the following query

      DimensionAttributeValueSet      dimensionAttributeValueSet;
    DimensionAttributeValueSetItem  dimensionAttributeValueSetItem;
    DimensionAttributeValue         dimensionAttributeValue;
    DimensionAttribute              dimensionAttribute;
    DimensionFinancialTag           dimensionFinancialTag;
    ProjTable                       projTable;

    while select RecId from projTable
        join RecId from dimensionAttributeValueSet
            where dimensionAttributeValueSet.RecId == projTable.DefaultDimension
               && projTable.ProjId == this.ProjId
        join RecId from dimensionAttributeValueSetItem
            where dimensionAttributeValueSetItem.DimensionAttributeValueSet == dimensionAttributeValueSet.RecId
        join RecId from dimensionAttributeValue
            where dimensionAttributeValue.RecId == dimensionAttributeValueSetItem.DimensionAttributeValue
        join RecId from dimensionAttribute
            where dimensionAttributeValue.DimensionAttribute == dimensionAttribute.RecId
//&& dimensionAttribute.Name == 'A_Managing_Group' //Un Comment to get specific value of Financial Dimension
        join Description from dimensionFinancialTag
            where dimensionFinancialTag.RecId == dimensionAttributeValue.EntityInstance
{
    info(strfmt('%1', dimensionFinancialTag.Description));
}

Monday, October 27, 2014

Expanding favorites node on startup

My Favorites node is collapsed while AX environment workspace is created, which is the out of box functionality. However, with small code hookup you can make the My Favorites node as expanded when workspace window is created.

In order to apply the tweak follow the following steps.

Step 1

Add the following code line in Info -> workspaceWindowCreated function after super call.

if (infolog.navPane())
{
    infolog.navPane().expandFavoriteNode('My favorites');
}


Step 2

Compile the class and Generate Incremental IL

Step 3


Now open a new workspace.

After

Before

Executing a Job through X++

There is a possibility of calling a job from X++, but using a job instead of a class has only disadvantages (e.g. jobs aren't able to run on server side). Don't use jobs in any production code.

In general, you have two possibilities - to create a menu item and call it:

Option 1

//you can change the type of called object without changing the invocation code
MenuFunction::runClient(menuItemActionStr(YourMenuItem), MenuItemType::Action);

Option 2

Run an application object via TreeNode:

#AOT

TreeNode::findNode(strFmt(#JobPath, identifierStr(YourJob))).AOTrun();

Scheduling a simple logic in Batch

This post shows the steps for executing a simple class in Batch processor.

Step 1 

Create a simple class which extends from "RunBaseBatch" class with following methods.

class Batch4DemoClass extends RunBaseBatch
{
}

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

public void run()
{
    // The purpose of your job.
        info(strFmt("Hello from Batch4DemoClass .executed at %1"
            ,DateTimeUtil ::toStr(
                DateTimeUtil ::utcNow())
            ));
}

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

Step 2 

Now create a simple job to add the class execution in Batch processor.

BatchHeader     batchHeader;
BatchInfo       batchInfo;
RunBaseBatch    runbasebatchTask;
str             sParmCaption = "My Demonstration (b351)";
    
//Creating Batch4DemoClass object
runbasebatchTask = new Batch4DemoClass();
    
//Assigning BatchInfo from RunBaseBatch task
batchInfo = runbasebatchTask.batchInfo();
batchInfo.parmCaption(sParmCaption);
batchInfo.parmGroupId(""); // The "Empty batch group".
    
//Creating BatchHeader and adding it to BatchQueue
batchHeader = BatchHeader::construct();
batchHeader.addTask(runbasebatchTask);
batchHeader.save();


info(strFmt("'%1' batch has been scheduled.", sParmCaption));





Read-only DocuRef form for Auditors

The most common problem encounters while auditing period in companies is not to allow auditors to attach or delete attachments from DocuRef form. However, there is no out of the box role in AX, but it can be achieved with small customizations and by applying the modified duplicated copy of system role and privilege.

Step 1


Make duplicate copy of “SystemUser” role and “DocumentHandlingEssentials” privilege. In my case, I have duplicated with the name “MySystemUser” and MyDocumentHandlingEssentials”.

Step 2

In “MyDocumentHandlingEssentials” remove all entry points accept “DocuView”.

Step 3

Now assign “MyDocumentHandlingEssentials” privilege to “MySystemUser” role and make sure “DocumentHandlingEssentials” is not overriding it.

Step 4

Now go to “DocuView” form and change the “NeedPermission” property for both “New” and “Delete” command buttons to “Delete”.



You are done with the changes, now assign this customized “MySystemUser” to Auditor user and remove the default “SystemUser” role.



Note: In some AX builds you may find a privilege with name “ClientEssentials”, it too contains all “DocuRef” entry points, so if you find one in your build, apply the same process of duplicating and removal of all “DocuRef” related entry points from it.

Friday, October 24, 2014

Fetching file name from the file path using X++

AX has a standard method in the Global Class to split the file name from the entire file path.The method is fileNameSplit().The method takes filepath as the parameter.It splits the filepath into three different strings filepath, filename and fileextension respectively.The usage of the function can be seen below.


    str    filepath; 
    str    filename; 
    str    fileType; 
    str    fileNameString;

    filepath = @'C:\Users\imran\AppData\Local\Text Document.txt';
    
    info(strFmt('File path  : %1', filepath));
    [filepath, filename, fileType] = fileNameSplit(filepath); 
    
    fileNameString= filename + fileType; 
    
    info(strFmt('File name : %1', filename));
    info(strFmt('File name with extension : %1', fileNameString));

Results:

   

Sunday, May 25, 2014

Creating new Sales Pickinglist X++

On recent project, customer put up a requirement which was pretty unusual among common scenarios of sales picking. Customer wants a new sales pickinglist on cancellation of items on a single picking route. 
In order to achieve this functionality let have a look on the tables involved in maintaining data of sales pickinglist. 

  • WMSShipment 
  • WMSPickingRoute
  • WMSPickingRouteLink
  • WMSOrder
  • WMSOrderTrans

WMSShipment 

The WMSShipment table contains information about shipments.

WMSPickingRoute

The WMSPickingRoute table contains information about picking routes. The picking routes are used by the warehouse management system to control the picking process.


WMSPickingRouteLink

The WMSPickingRouteLink table contains information about which sales orders and which picking routes are related.


WMSOrder

The WMSOrder table contains information about orders that are handled by the warehouse management system.


WMSOrderTrans

The WMSOrderTrans table contains information about order lines and items that are handled by the warehouse management system. This table is used for item picking, shipping, and pallet transportation.

Saturday, May 24, 2014

Getting address by type X++

This post shows the code which helps in retrieving the address of Party (Customer, Vendor etc.) by providing the type of addresses.

Function which takes Party and LogisticsLocationRoleType (Invoice, Delivery, Shipping etc.)

public static LogisticsPostalAddress getPostalAddressByType(DirPartyRecId _party, LogisticsLocationRoleType _type)
{
    DirPartyLocation        partyLocation;
    DirPartyLocationRole    partyLocationRole;
    LogisticsLocation       location;
    LogisticsLocationRole   locationRole;
    LogisticsPostalAddress  postalAddress;

    select firstonly postalAddress
        exists join location
            where location.RecId == postalAddress.Location
        exists join locationRole
            where locationRole.Type  == _type
        exists join partyLocation
            where 
                partyLocation.Location == location.RecId &&
                partyLocation.Party == _party
        exists join partyLocationRole
            where partyLocationRole.PartyLocation == partyLocation.RecId &&
                partyLocationRole.LocationRole == locationRole.RecId;

    return postalAddress;
}

Sunday, May 18, 2014

Table event execution sequence in Dynamics AX

This post will provide information of sequence of event triggering while working on Table Browser and Data in Dynamics AX.

Field data change


Deleting record


Updating record


Saving record



When Table Browser opens


Pressing Ctr+N on Table Browser


Sunday, May 11, 2014

How Ship Carrier works in AX

Introduction

Microsoft Dynamics AX customers often use one or more shipping carriers to deliver their products to customers. Shipping software is available directly from these carriers, such as UPS and FedEx that can work with many carriers and centralize the shipping process. This software is used to calculate shipping rates, produce labels and tracking numbers, and generate additional reports and documents.
The shipping carrier interface feature allows Microsoft Dynamics AX customers to integrate with shipping software. The shipping software packages can pull information from Microsoft Dynamics AX, process packages, and then move their information into Microsoft Dynamics AX, eliminating manual entry and improving tracking visibility

Technical  Working

Step 1

The process begins with Packingslip functionality in AX. At this stage a request is generated by AX and saved in ShippCarreirShippingRequest table.

The following code inserts the request in ShipCarrierShippingRequest table.

Classes -> SalesPackingSlipJournalPost -> endPost -> line # 7


Saturday, March 22, 2014

Time consumed function in Axapta

Performance measure in AX is a quite tough job and to achieve this job some built-in functions of AX can be used to determine the execution time of business logic.

TimeConsumed() a very useful function in Global class which we can be used to calculate the time taken to execute business logic in AX 2012. This function will return total time consumed in string format “X hours X minutes X seconds”. Note: It does not shows –negative value.
It handles up to a 24 hour time difference not dependent on start/end time. If time consumed > 24 hours will only report time over 24 hour intervals.

Demonstration

FromTime        fromTime;
ToTime          toTime;

fromTime    = timeNow();
toTime      = timeNow() + 50000;

//Timeconsumed()
info(strFmt('Time consumed %1', timeConsumed(fromTime, toTime)));



Saturday, February 15, 2014

Highlighting record in Grid

It is possible to use colours in grids on Axapta forms. This can be a useful method to highlight particular rows, or grid cells within a row, based on the data in the record.

The method displayOption() method on a form datasource can be over-ridden to control the appearance of an single row in a grid. It is possible to set the background or foreground (text) colour. Since this is a form datasource method, the FormRowDisplayOption affects not only the grid, but also the other controls on this form connected to the datasource.

This method is executed once for each record before the record is displayed on a form.

The following example overrides the displayOption method to set display options, and to override the background color for the particular record.

public void displayOption(Common _record, FormRowDisplayOption _options)
{
     SalesLine    salesLineLocal;
     if (salesLineLocal.ItemGroup == 'ABC')
     {
         _options.backColor(WinApi::RGB2int(255,255,0)); // Yellow  
     }
}