to remove the warning add literalStr() for label string validation.
Where the label has “Item length: %1”
warning( strFmt( literalStr("@xxx:xyxyxy"), inventDim.InventColorId ) );
to remove the warning add literalStr() for label string validation.
Where the label has “Item length: %1”
warning( strFmt( literalStr("@xxx:xyxyxy"), inventDim.InventColorId ) );
Good to go now…
Please run the following SQL script on each copy of your AX instance, transaction database. This should release around 45 GB of disk space from your database.
use DEV
truncate table BATCHJOB;
truncate table BATCH;
truncate table BATCHJOBALERTS;
truncate table BATCHSERVERGROUP;
truncate table BATCHHISTORY;
truncate table RSAADPCalculationDetail;
truncate table AIFDocumentLog;
truncate table AIFMessageLog;
DBCC SHRINKFILE (N’AXCNFGCU8′ , 44723)
GO
You need to change the USE statement to your database instance.
On recent enhancement there was a scenario that for some condition I need to reset the production order status from Estimated/Scheduled to created and need to delete the production order. The reference to this production order also should be removed from sales order.
I wrote the following code
Try
{
ttsBegin;
ProdTable = prodtable::find(_prodId, true);
If (prodTable)
{
prodTable.prodStatus = ProdStatus::created;
if (prodTable.validateWrite())
prodTable.update;
}
ttscommit;
}
Catch(exception::error)
{
checkFailed(strFmt(@”Delete of Production order %1 is failed.”, _prodId));
}
after resetting this I deleted the production order and it deletes the coproduct records too.
I thought it worked fine. But actually it deletes the Production line reference from inventory transaction and not co-product reference from inventory transaction.
So I debugged more and I found the right code which deletes all the references related to production order from transaction.
This below code deletes all the inventory transaction related to production order when we change the status to created.
Public static void resetProdStatus(prodId _prodId)
{
ProdMultiStatusDecrease prodMultiStatusDecrease;
ProdParmStatusDecrease prodParmStatusDecrease;
ProdTable prodtable;
Args args = new Args();
;
Try
{
ttsbegin;
prodTable = ProdTable::find(_prodId,true);
if (ProdTable.ProdStatus > prodStatus::Created)
{
args.record(ProdTable);
select prodParmStatusDecrease where prodParmStatusDecrease.ProdId == ProdTable.ProdId;
if (!prodParmStatusDecrease.RecId)
{
prodParmStatusDecrease.clear();
prodParmStatusDecrease.initFromProdTable(ProdTable);
prodParmStatusDecrease.WantedStatus = ProdStatus::Created;
prodParmStatusDecrease.ParmId = NumberSeq::newGetNum(CompanyInfo::numRefParmId()).num();
prodParmStatusDecrease.insert();
}
prodMultiStatusDecrease = prodMultiStatusDecrease::construct(args);
prodMultiStatusDecrease.initParmBuffer(prodParmStatusDecrease);
prodMultiStatusDecrease.parmId(prodParmStatusDecrease.ParmId);
prodMultiStatusDecrease.run();
}
ttsCommit;
}
Catch (Exception::error)
{
checkFailed(strFmt(@”Delete of Production order %1 is failed.”, _prodId));
}
}
Hope it helps.
Here is the scenario where user wants to open the Listpage from a infolog. This can be achieved by SysInfoAction class.
static void infoSO(Args _args)
{
Query q = new Query(queryStr(SalesTableListPage));
SalesId salesorders;
salesTable salestable;
while select salestable where salestable.salesStatus == SalesStatus::cancelled
{
salesOrders += salesTable.salesid + “,”;
}
Salesorders = strdel(salesorders,strlen(salesorders)-1,1);
q.dataSourceTable(tableNum(SalesTable)).addRange(
fieldNum(SalesTable, salesId(salesid));
info(‘Click Show to open list page’, ”, SysInfoAction_FormrunQuery::newFormnameQuery(formStr(SalesTableListPage), q));
}
Chain of command:
On platform update 9 of Dynamics AX for Operations, we have a new extension possibility called chain of command (CoC). Now we are able to add pre and post functionality to extensible methods in a much easier and readable manner than the previously used event handlers we are now able to access protected methods and variables directly in the extended class without problems.
In other words, it allow developers to wrap logic around methods defined in a base class. This allows extending the logic of public and protected methods without the need to use event handlers.
Now let us check how it works with an example.
Let us take class as an example.
Condition:
The method should exists in the Class for which you are going to use CoC.
For demo Iam taking salesLinetype\ initvalue method. Here I need to assign some default value into the sales line table while creating sales line. Since standard class methods cannot be modified, we need to create either Pre-handler or Post-Handler method to achieve this. Instead of writing event handler COC comes into picture where we can achieve this in single method.
Create a new SalesLineTypeClass_extension. Final should be prefix. Above the class specify the Extensionof Keyword followed by Type of Extension [Class/form] and Name of the object[Class / Form] .
We use exactly the same notation as the original method we are extending and add a mandatory “next” keyword to the call, which will wrap our extension code around the extended method. This next keyword separates the pre and post parts of our extension and mandatory to be called inside the chain of command method.
Note: Next call cannot be placed in any kind of program block like if() block or while block.
When you call next(), it checks for other extensions in the queue and runs them in random order, lastly running the base code.
The code before next initvalue () will work as pre handler of initvalue method.
The code after next initvalue () will work as post handler of initvalue method.
Here there is an issue in assignment statement. We cannot get the sales line reference from Standard class.
To achieve this we have two options:
[ExtensionOf(classstr(SalesLineType))]
final class SalesLineType_Extension
{
void initValue()
{
this.parmsalesLine().SalesQty = 5;//Pre handler
next initvalue();
this.parmsalesLine().SalesQty = 10;//Post handler
}
2. create a new method like getSalesline() which will return this.salesline;
[ExtensionOf(classstr(SalesLineType))]
final class SalesLineType_Extension
{
public Salesline getsalesLine()
{
return this.salesLine;
}
void initValue()
{
this.getsalesLine().SalesQty = 5;//works like Pre handler
next initvalue();
this.getsalesLine().SalesQty = 10;//works like Post handler
}
}
output:
Create a salesline and check the sales qty will be defaulted to 10 if code written after next else it should be 5 or standard 1 qty.
Here is a way to remove special characters from a string.
static void RemoveAllSpecialChararcters(Args _args)
{
str sometext = “ABC#DE%F_$#G@1&23″;
str x = System.Text.RegularExpressions.Regex::Replace(sometext, @”[#,_,$,%,@,&]”, “”);
info(x);
}
Output: ABCDEFG123
I found some useful job and sharing it in my blog which will be used for developers.When we do full sync. some of the objects will not be sync. properly. Object information will be shown in Synchronization log form. Run the following job which will combine each object type and will give the Number(batch Num) which needs to execute individually.
static void updateNotSynced(Args _args)
{
SysVersionControlSynchronizeLog syncLog;
SysVersionControlSynchronizeBatchNum lastNum;
boolean entered = false;
//find last batch num
select firstOnly BatchNum from syncLog
order by syncLog.BatchNum desc;
lastNum = syncLog.BatchNum;
setPrefix(‘Numbers to process:’);
//classes
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Classes*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for classes is: %1’, lastNum));
}
//forms
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Forms*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Forms is: %1’, lastNum));
}
//tables
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Data dictionary*Tables*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Tables is: %1’, lastNum));
}
//extended data types
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Data dictionary*Extended data types*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for EDTs is: %1’, lastNum));
}
//base enums
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Data dictionary*Base enums*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Enums is: %1’, lastNum));
}
//menu items
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Menu items*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Menu items is: %1’, lastNum));
}
//shared projects
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Projects*Shared*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Shared projects is: %1’, lastNum));
}
//jobs
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Jobs*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Jobs is: %1’, lastNum));
}
//SSRS Reports
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*SSRS Reports*Reports*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for SSRS reports is: %1’, lastNum));
}
//Security roles
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Security*Roles*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Roles is: %1’, lastNum));
}
//Security duties
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Security*Duties*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Duties is: %1’, lastNum));
}
//Security privileges
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Security*Privileges*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Privileges is: %1’, lastNum));
}
//Menus
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Menus*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Menus is: %1’, lastNum));
}
//Services
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Services*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Services is: %1’, lastNum));
}
//Code permissions
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Security*Code permissions*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Code permissions is: %1’, lastNum));
}
//Workflow
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Workflow*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Workflows is: %1’, lastNum));
}
//Parts
ttsBegin;
while select forUpdate syncLog
where syncLog.Processed == NoYes::No &&
syncLog.ItemPath like ‘*Parts*’
{
syncLog.BatchNum = lastNum + 1;
syncLog.update();
entered = true;
}
ttsCommit;
if (entered)
{
lastNum++;
entered = false;
info(strFmt(‘Batch number for Parts is: %1’, lastNum));
}
}
On recent enhancement there was a scenario that for some condition I need to reset the production order status from Estimated/Scheduled to created and need to delete the production order. The reference to this production order also should be removed from sales order.
I wrote the following code
Try
{
ttsBegin;
ProdTable = prodtable::find(_prodId, true);
If (prodTable)
{
prodTable.prodStatus = ProdStatus::created;
if (prodTable.validateWrite())
prodTable.update;
}
ttscommit;
}
Catch(exception::error)
{
checkFailed(strFmt(@”Delete of Production order %1 is failed.”, _prodId));
}
after resetting this I deleted the production order using delete_from and it deletes the coproduct records too.
I thought it worked fine. But actually it deletes the Production line reference from inventory transaction and not co-product reference from inventory transaction. Filter the production order that was reset recently in IM > Inquiries > Transaction. you can see the co-product reference is not deleted.
So I debugged more and I found the right code which deletes all the references related to production order from transaction.
This below code deletes all the inventory transaction related to production order when we change the status to created.
Public static void resetProdStatus(prodId _prodId)
{
ProdMultiStatusDecrease prodMultiStatusDecrease;
ProdParmStatusDecrease prodParmStatusDecrease;
ProdTable prodtable;
Args args = new Args();
;
Try
{
ttsbegin;
prodTable = ProdTable::find(_prodId,true);
if (ProdTable.ProdStatus > prodStatus::Created)
{
args.record(ProdTable);
select prodParmStatusDecrease where prodParmStatusDecrease.ProdId == ProdTable.ProdId;
if (!prodParmStatusDecrease.RecId)
{
prodParmStatusDecrease.clear();
prodParmStatusDecrease.initFromProdTable(ProdTable);
prodParmStatusDecrease.WantedStatus = ProdStatus::Created;
prodParmStatusDecrease.ParmId = NumberSeq::newGetNum(CompanyInfo::numRefParmId()).num();
prodParmStatusDecrease.insert();
}
prodMultiStatusDecrease = prodMultiStatusDecrease::construct(args);
prodMultiStatusDecrease.initParmBuffer(prodParmStatusDecrease);
prodMultiStatusDecrease.parmId(prodParmStatusDecrease.ParmId);
prodMultiStatusDecrease.run();
}
ttsCommit;
}
Catch (Exception::error)
{
checkFailed(strFmt(@”Delete of Production order %1 is failed.”, _prodId));
}
}
I came across a scenario where customer need to remove the trailing zero’s in decimal places if exists. Since Ax doesn’t have the functions to remove , I created a new method which will remove the zeros.
Public void RemoveTrailingZeros(args _args)
{
str txt = ‘0.1823’;
str ch = ‘0’;
str result;
str tmp;
int i = strLen(txt);
while(true)
{
tmp = subStr(txt, i, 1);
if(i && tmp == ch)
{
i–;
}
else
{
break;
}
}
result = strDel(txt, i+1, maxInt());
info(result);
}
I have set no. of decimal places to 4.
Example 1:
Str txt = ‘0.1800’;
Result : 0.18
Example 2:
Str txt = ‘0.1850’;
Result : 0.185
Example 3:
Str txt = ‘0.1854’;
Result : 0.1854