Showing posts with label Classes. Show all posts
Showing posts with label Classes. Show all posts

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);
}

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?

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.

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

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();
}

Friday, March 30, 2012

SalesTableForm Class

Customizing a Sales Order is one of the most frequent tasks in any Dynamics AX Implementation.
Form SalesTable is anyways a heavily customized form and writing any direct code on it (or for that matter on any form) is not advised.

So what could you do? Well that depends on what you want to do? :)

Writing code on a table or a class is what is best practice. In case of Sales Order form, code could be written on either SalesTable/SalesLine tables or SalesTableType/SalesLineType classes. But then there is only so much you can achieve writing code in the above two places.

In a recent interaction with Mr Lars Jacobsen from Norway, I discovered another way.
There is another class called SalesTableForm Class, which is recommended for writing code that affects your Sales Order form, header as well as lines.
For eg, if you need to dynamically create a new sales line when you satisfy some condition, you can write your code in the method SalesLine_write(). Do explore the class to figure it out more.