Friday, October 18, 2013

Job to fetch sales orders with lot of lines

A quick job to fetch sales orders with lot of lines.

static void salesTableFetch(Args _args)
{
    SalesLine     salesLine;    
    ; 
   
    while select count(recId), SalesId from salesLine
        group by SalesId

    {
        if (salesLine.RecId > 100)
        {
            info(salesLine.salesId);
        }
    }
}

Thursday, September 26, 2013

jumpRef variations

jumpRef method is overriden for enabling the 'View details' feature, when you right click on a field.
If your datasource on a form has relations to multiple tables with the same field, the form may choose the undesired table to reference.

You can do this either on the datasource field or the form control. Once you have overridden the method add code similar to the example below.
    Args    args;
    FormRun  formRun;

    args = new Args(formstr(VendTable));
    args.record(VendTable::find("1000629"));
   
    formRun = classfactory.formRunClass(args);
    formRun.init();
    formRun.run();
    formRun.wait();
    formRun.detach();  
    

Notice the record parameter, it is the actual table record you want to open.

Imagine a situation where you open a form B by clicking a button on form A. The clicked method of the button had the above code. And you want to perform some operation after the form B is closed. You could use the closedOk method of form B and call any method on form A like below.
    if (formRun.closedOk())
        MMSTmpVendPrincipalDistributor_ds.executeQuery();

Instead of formRun, a more secure and neater approach would be to use the MenuFunction like below. Its secure because it uses the menu item on which we can control access. copyCallerQuery() is optional. In some instances, when you have a grid of a different datasource than the main datasource with no relation to main datasource table, its records wont be populated if you don't use copyCallerQuery.
    menuFunction = new MenuFunction(MenuItemDisplayStr(VendTable), MenuItemType::Display);   
    menuFunction.copyCallerQuery(CopyCallerQuery::Yes);
    menuFunction.run(args);

AX also provides two classes, smmUtility and MenuFunction, which provide out of the box methods to achieve above functionality. Some examples picked up from standard AX.

smmUtility: openMenuItemForm and performJumpRef
smmUtility::openMenuItemForm(menuitemDisplayStr(DirPartyTable),vendTable,element,false);

smmUtility::peformJumpRef(tablenum(smmQuotationReasonGroup), smmQuotationReasonGroup::find(this.text()).RecId);

MenuFunction: runClient
public void jumpRef()
{
    Args argsProductForm;

    argsProductForm = new Args();
    argsProductForm.record(EcoResProduct);
    argsProductForm.caller(element);
    argsProductForm.copyCallerQuery(CopyCallerQuery::No);

    MenuFunction::runClient(menuitemDisplayStr(EcoResProductDetails),MenuItemType::Display,false,argsProductForm);
}

Thursday, September 19, 2013

Using Macros - #localmacro #if and #define

Most of us are familiar with Macros. Unfortunately, they are seldom used to their potential.
From MSDN, 
A macro is a variable known to the precompiler. The variable can have a value that is a sequence of characters, but it is not required to have a value. The #define directive tells the precompiler to create the macro variable, including an optional value. The #if directive tests whether the variable is defined, and optionally, whether it has a specific value.

 static void SimpleDefineIfJob(Args _args)  
 {  
   str sTest = "Initial value.";  
   ;  
   #define.MyMacro // MyMacro is now defined.  
   #if.MyMacro  
     sTest = "Yes, MyMacro is defined.";  
     info(sTest);  
   #endif  
   // Notice the non-code sentence line causes no X++ compiler error,  
   // because the X++ compiler never sees it.  
   #ifnot.MyMacro  
     The X++ compiler would reject this sentence.  
     sTest = "No, MyMacro is not defined.";  
     info(sTest);  
   #endif  
 }  

Again from MSDN,
The #localmacro directive is a good choice when you want a macro to have a value that is several lines long, or when your macro value contains a closing parenthesis. The #localmacro directive is a good choice when you want your macro value to be lines of X++ or SQL code.
One particular use i found is when you want to suppress or replace particular where clauses from a select statement. #localmacro works wonderfully.

 static void LocalMacroInSelect(Args _args)  
 {  
   VendTable            vendTable;  
   boolean             showAllRecords = false;  
   #localmacro.WhileLoopFilter  
   while select firstOnly10 vendTable  
     %1  
     {  
       info(vendTable.accountNum);  
     }  
   #endmacro  
   if (showAllRecords)  
   {  
     #WhileLoopFilter(where vendTable.blocked == CustVendorBlocked::All)  
   }  
   else  
   {  
     #WhileLoopFilter(where vendTable.blocked == CustVendorBlocked::Payment)  
   }  
 }  

Read Enum Elements

A quick job to fetch enum elements using a for loop. Much better than having to manually type the values.

 static void EnumValues(Args _args)  
 {  
   DictEnum      enum = new DictEnum(enumName2Id("ABC"));  
   int         i;  
   for (i=0; i < enum.values(); i++)  
   {      
     info(strFmt("%1-%2", enum.index2Label(i), enum.index2value(i)));   
   }   
 }  

Wednesday, July 24, 2013

Playing with controller class

SysOperation framework relies on SysOperationServiceController class for passing the args argument from the menu item to the framework. Overriding some of the methods on this class gives you more control over its functioning. Same way when you want to modify the user interface of a SysOperation service, UI Builder classes are the way to go. Two methods of particular interest to me are:
showQueryValues When a query is used and this method returns true, then the fields with ranges and a Select button will be shown on the dialog. Return false in this method to hide them.
showQuerySelectButton This method does the same as the showQueryValues method, but only affects the Select button.

Some cases we want to create a batchable job, which a user can use but don't want to give him the option to 'select' any particular records or its meaningless to show the query fields on dialog. Overriding the above two methods to return false can solve the purpose.

You will also need to override main and construct methods in this case, like below.

 public static void main(args _args)  
 {  
   MMSPriceUpdatePublishDataController  controller = MMSPriceUpdatePublishDataController::construct();  
   controller.startOperation();  
 }  
 private static MMSPriceUpdatePublishDataController construct(SysOperationExecutionMode _mode = SysOperationExecutionMode::ReliableAsynchronous)  
 {  
   MMSPriceUpdatePublishDataController  controller = new MMSPriceUpdatePublishDataController(classStr(MMSPriceUpdatePublishData),methodStr(MMSPriceUpdatePublishData, run), _mode);  
   return controller;  
 }  
 public boolean showQuerySelectButton(str parameterName)  
 {  
   return false;  
 }  
Also set the Object property on the menu item as the name of your controller class.

Wednesday, July 17, 2013

Posting trade agreements

Posting trade agreements is not a straightforward thing and i found it during a recent assignment. Trade agreements broadly revolve around three main tables, PriceDiscAdmTable, PriceDiscAdmTrans and PriceDiscTable and posting is handled via class PriceDiscAdmCheckPost.

So i create a journal header and fill the journal lines (details in another blog maybe) and call the below method to do the posting.

 void postTradeAgreement(PriceDiscAdmTable  _priceJourHeader)  
 {  
   PriceDiscAdmCheckPost    jourPost;  
   PriceDiscTable            tradeTable;  
   InfologData               infologData;  
   //Post  
   jourPost = new PriceDiscAdmCheckPost(false);  
   infologData = infolog.infologData();          // store infolog data  
   infolog.clear();                    // if you don't clear the infolog, posting errors out.  
   jourPost.initJournalNum(_priceJourHeader.JournalNum);  
   jourPost.run();  
   infolog.import(infologData);              // import infolog data  
 }  

See anything strange?
I do three things related to Infolog.
1. Store the contents. I am storing the contents of infolog (my logic needs to show info to user later) in a data type InfologData, which is a container.

2. Clear the infolog. If i dont clear the infolog just before i hit run() method to post, i get an error which makes no sense.

3. Import the infolog contents again.

Anybody knows a better way?

Default unit on a Product

While writing logic to create new products through code, there was a discussion to have the Purch/Invent/Sales unit id on a Product to be defaulted to ‘Each’. And idea was to hardcode it.
This unit id is required by AX for trade agreements creation.

While trying to code this, we found a parameter setting on Inventory parameters form that lets the user parameterise it instead of harcoding.


Wednesday, July 3, 2013

List page controls design

This would be a quick post. More so for my remembrance than for enlightening public in general :)

Controlling the visibility of a form's controls and action pane buttons is something very common.
But doing so for a list page is not so common.

For a normal form, to make a field invisible you would write something like below in form's init() method.

 VendTable_ds.object(fieldNum(VendTable, yourField)).visible(false);  

For a list page, you would write something like below in that list page's interaction class's initialized() method.

 this.listPage().listPageFieldVisible(formControlStr(VendTableListPage, VendTable_YourField), false);  

More features
this.listPage().listPageArgs().menuItemName() - would let you control the list page's behavior depending on which menu item is your page getting called from.

this.listPage().actionPaneControlVisible and this.listPage().actionPaneControlEnabled - for controlling action panes behavior.

I refered VendTableListPageInteraction class for this post. Cheers.

Cross company traversing

Today's discussion is around traversing a table across companies. In our example, a record sits in a custom parameters table, which needs to be updated. Instinctively first thing to check would be  the SaveDataPerCompany property of the table, which in our case is set to Yes. So this table stores records per company and for implementing the feature in discussion we will forcefully update the record across all companies by overriding the table's update method.

Logic centers around
1. Traversing DataArea table, which is a system table containing all legal entities
2. Using keyword changeCompany which swaps the companies one by one and
3. Clearing the table object after each change, failing to do so makes the logic not work.

 public void update()   
 {   
   YourTable yourtable;  
   DataArea DataArea;   
   super();   
   while select DataArea where !DataArea.isVirtual   
   {   
     changecompany (DataArea.Id)   
     {   
       yourtable= null;   
       ttsBegin;   
       yourtable= Yourtable::find(true);        
       if (yourtable.RecId)   
       {   
         yourtable.Field = "";   
         yourtable.update();   
       }   
       ttsCommit;   
     }   
   }   
 }  

Monday, May 6, 2013

X++ Vs CIL


In one of the assignments, I had to make a batch job using Sys operation framework.
Requirement was to create and post trade agreements, and for that I used PricePriceDiscJourService.
So I created a service class, a data contract class, a service, a query and a menu item. All standard AX objects required for a batch job to run.

The issue was that if i put all my methods/logic  in a standalone class, everything worked fine. That means records get created in all the right tables like PriceDiscAdmTrans, PriceDiscAdmtable, and PriceDiscTable. But if try to run the batch job (after CIL compile of course), it doesn’t create all the records. Some records were getting skipped, which didn’t make sense since my standalone class and the batch job class were logically speaking the same. 
I used a while(query.next()) to loop through two header/line tables and fetch records.

Now the fun part. Debugging in Visual Studio (since it’s a service you can’t debug in X++) i found that the while loop works little differently in Visual Studio than in X++. In Visual Studio, the line table's RecId gets reset even when the while loop moves to the next parent record when in fact you would expect it to be there till the cursor moves to the line record. AX, as expected, keeps the RecId value until the line table gets reassigned the next table record. My logic depended on this and it failed. Finally I changed the logic to store the table buffer in a variable and managed to get the code working.

Does anyone have a similar experience or any suggestions?




Sunday, February 17, 2013

Default Descriptions

Quoting from MSDN,
“You can set up default text that is used to fill in the Description field for accounting entries that are posted automatically to the general ledger. Use the Default descriptions form to select the parameters or fields that are used to create the description text.”


Organization administration > Setup > Default descriptions.
In the Text field, enter the default description. You can type text in the field, or you can use one or more of the following free text variables:
·        %1 – Add the transaction date.
·        %2 – Add an identifier that corresponds to the document type that is being posted to the general ledger. For example, for transaction types that are related to invoices, the %2 variable adds the invoice number.
·        %3 – Add an identifier that is related to the document type that is being posted to the general ledger. For example, for transaction types that are related to invoices, the %3 variable adds the customer account number.
You can also define three more variables %4-%6, whose values differ from record to record. In case you are creating your own description (needs customization), you can define what values to come in these.

Behind the scenes
Class TransactionTxt drives this feature end to end. If you see the method txt(), the last line explains it all.  

return strfmt(txt, date2StrUsr(transDate, DateFlags::FormatAll), formLetterNum, voucherNum, key1, key2, key3);

In one of the customizations, I was required to populate description value on invoice transactions. I used a display method with below logic, the use of TransactionTxt class is self evident.



 

Saturday, February 16, 2013

Create a New Financial Dimension

AX 2012 supports unlimited number of Financial Dimensions. System entities like Projects, Customers, Customer groups etc can be made into Dimensions. Form opens at General ledger > Setup > Financial dimensions > Financial dimensions. One option is to create a user-defined financial dimension in the Use values from field by selecting < Custom dimension >. Another way it to customize AX to include your own dimensions.
For example, one requirement i got was to have Mode of Delivery as a Financial dimension.


So what needs to be done?

Luckily, only thing required is to create a view.

1. Create a view named DimAttributeModeOfDelivery. Why such a name, little later.
2. Define its datasource name as BackingEntity.
3. Define three fields on it namely, Key(RecId), Value(Code like Id) and Name


Whats going on here?
Form DimensionDetails's run method calls Class DimensionDetails.run() method which calls a method DimensionEnabledType::getSystemDefinedDimensions()


Next call is made to DimensionEnabledType::getSystemDefinedDimensionsServer()

Here the code loops through and if an entity is a view and matches proper naming conventions (prefix DimAttribute*), considers it "dimension enabled"


Multiple Designs in SSRS

Developers who have had their hands dirty playing with SSRS in 2012 must be well aware of the Report Data Provider framework. RDP is the new way AX wants you to make reports in SSRS. In a way, Microsoft wants to put all the business logic back in X++ and dissuade developers from using the Visual Studio data methods. In case, all this sounds greek to you, please browse MSDN. 

Few important concepts here: 
  • Temporary table – RDP class fills a temporary table with data that will be used by Reporting Services to display the report. 
  • Data Contract Class – defines the parameters in the report. 
  • Report Data Provider Class – processes business logic based on a query and parameters defined in the data contract class, and then returns the tables as a dataset for the report. 
  • Controller class – control the report execution and dialog forms. Report controllers can be used to modify report dialogs, validate report parameters and other validations necessary before report execution. 
  • UIBuilder class – enhance the dialog more. 

Today, I will touch upon a small but interesting topic related to this. One of the reports I was required to have two designs Summary/Detailed and based on the value in Summary checkbox in Dialog, decide at run-time which design to display. This logic resides in the Controller class, in method preRunModifyContract().

protected void preRunModifyContract()
{
    SalesMarginReportContract   dataContract = this.parmReportContract().parmRdpContract() as SalesMarginReportContract;

    if (dataContract.parmIsSummary())
    {
        this.parmReportContract().parmReportName(ssrsReportStr(SalesMarginReport, SummaryDesign));

    }
    else
    {
        this.parmReportContract().parmReportName(ssrsReportStr(SalesMarginReport, DetailedDesign));
    }

    super();
}

Up Down Sequence Logic

In one of the customizations, there was a requirement to allow users to move items up/down in BOMDesignerDetails Form. This form uses a Tree control, which displays a BOM and its items in a tree structure. A user should be able to resequence these items with Up and Down arrows. Sounds simple and it is, once you know how. There is a standard form EConDesigner which has this feature and I replicated the code for the two buttons. Code is in the clicked method of the buttons. Logic utilizes few standard Tree control methods like getSelection, getPrevSibling, getNextSibling, moveItem, selectItems. Below is a simpler description, our requirement was a little more complex requiring renumbering of LineNum as the sequence changes and simultaneous update to a Map, which stored the key, value pairs for each item. 



MoveUpButton
void clicked()
{
    int idx_mv = ModelTree.getSelection();
    int idx;
    ;

    if (idx_mv)
    {
        idx = ModelTree.getPrevSibling(idx_mv);
        if (idx)
        {
            idx_mv = ModelTree.moveItem(idx, ModelTree.getParent(idx_mv), idx_mv);
            ModelTree.selectItems(idx, idx);
        }
    }
}
MoveDownButton
void clicked()
{
    int idx_mv = ModelTree.getSelection();
    int idx;
    ;

    if (idx_mv)
    {
        idx = ModelTree.getNextSibling(idx_mv);
        if (idx)
        {
            idx_mv = ModelTree.moveItem(idx_mv, ModelTree.getParent(idx_mv), idx);
            ModelTree.selectItems(idx_mv, idx_mv);
        }
    }
}

Thursday, January 3, 2013

Global findByRecId() function

AX 2012 relies heavily on foreign key relations for its table relations. A consequence of this change is that now developers will be accessing tables using recIds rather than using any other primary key, which was the norm before. So far so good. So what's the problem, you may ask.
Now this will also mean that developers will need to write lot more findByRecId() methods on tables. That is redundancy. We can refactor this method as a generic template and write it in Global class.

One example I found in an excellent blog post.
http://www.doens.be/2009/07/select-a-record-from-a-table-when-you-only-have-the-tableid/

public Common findByRecId(TableId _tableId, RecId _recId, Boolean _forUpdate = false)
{
    Common      common;
    DictTable   dictTable;
    ;
    dictTable = new DictTable(_tableId);
    common = dictTable.makeRecord();
 
    common.selectForUpdate(_forUpdate);
 
    select common
    where common.RecId == _recId;
 
    return common;
}
Now when you need to access a record buffer in a table MyTable using record id, instead of writing it as MyTable::findByRecId(_recId), you can use findByRecId(tableNum(MyTable), _recId)

"Execute business operations in CIL" or not?

In AX 2012, steps are taken by the development team at Microsoft to align AX IDE with .NET. That's why you regenerate the CIL after X++ code changes.

CIL obviously has its benefits like faster execution in cases of heavy logic processing.

But sometimes while testing or debugging, developers would prefer NOT to avail this option. Debugging can be cumbersome in AX 2012 as you will need to configure Visual Studio for code running in CIL. The solution is to uncheck a checkbox in Options at
Tools > Options > Development > General > Execute business operations in CIL

Now even the code which was running in CIL will now run in X++ interpreted mode, the old way.

Word of caution, this option should only be used by developers while debugging. It should ideally be checked again after work is finished.

Read more at http://msdn.microsoft.com/en-us/library/hh528509.aspx