static void salesTableFetch(Args _args)
{
SalesLine salesLine;
;
while select count(recId), SalesId from salesLine
group by SalesId
{
if (salesLine.RecId > 100)
{
info(salesLine.salesId);
}
}
}
Thanks to the popularity of Microsoft Dynamics AX (aka Axapta), there are tons of blogs containing useful insights and interesting facts on AX. The community is ever-growing and feels great to be a part of it. With this blog, I will add my two cents to the knowledge base and enforce my concepts as well. I will focus more on relatively unknown facts about AX as the fundamental aspects are thoroughly covered. So, let's begin.
Friday, October 18, 2013
Job to fetch sales orders with lot of lines
A quick job to fetch sales orders with lot of lines.
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.
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.
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.
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
MenuFunction: runClient
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.
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?
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.
For a list page, you would write something like below in that list page's interaction class's initialized() method.
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.
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.
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.
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.
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,
Behind the scenes
Class TransactionTxt drives this feature end to end. If you see the method txt(), the last line explains it all.
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.
“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.”
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"
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/
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
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
Subscribe to:
Posts (Atom)