Blog

Name is Anant Dubey and the intent to create this blog is to discuss the problems and issues that developer face in the dynamics AX development and to share the new things that come up with the new version of AX.

Monday, December 13, 2021

Enter data from new line in SSRS and Formula in D365fo

 =Format(Fields!QuotationDate.Value, "MMM-dd yyyy") & Microsoft.VisualBasic.Constants.vbcrlf & Microsoft.VisualBasic.Constants.vbcrlf & Fields!CustomerName.Value & Microsoft.VisualBasic.Constants.vbcrlf & Fields!CustomerAddress.Value



 =Format(Fields!QuotationDate.Value, "MMM-dd yyyy") & vbcrlf & vbcrlf & Fields!CustomerName.Value & vbcrlf & Fields!CustomerAddress.Value


=Fields!PurchOrderDocNum.Value & vbcrlf & Format(Now(),"dd/MM/yyyy")


=IIF(Fields!CompLabelPrice.Value = "0.00",Nothing, Format(Fields!CompLabelPrice.Value, "#,0.00;(#,0.00)")) & " /-"

--------------------------------------------------------------------------------------------------------

To Get user specific date time on report

-

=Microsoft.Dynamics.Framework.Reports.DataMethodUtility.ConvertUtcToAxUserTimeZoneForUser(Parameters!AX_CompanyName.Value, Parameters!AX_UserContext.Value, System.DateTime.UtcNow, "d", Parameters!AX_RenderingCulture.Value) & vbCrLf & Microsoft.Dynamics.Framework.Reports.DataMethodUtility.ConvertUtcToAxUserTimeZoneForUser(Parameters!AX_CompanyName.Value, Parameters!AX_UserContext.Value, System.DateTime.UtcNow, "t", Parameters!AX_RenderingCulture.Value)

--------------------------------------------------------------------------------------------------------

Print every word with new line

=Replace(Fields!Text2.Value," ", System.Environment.NewLine)

--------------------------------------------------------------------------------------------------------


Print Page number on SSRS in D365 FO

Visual studio expression to print page number on every pages of report


 ="Page "&Globals!PageNumber &" of "&Globals!TotalPages

Saturday, December 11, 2021

Insert User ID in All user group in D365fo

 Assign user id in All user group in D365 FO


insert into USERGROUPLIST ( USERID, GROUPID)

select 'anant.dubey', GROUPID from USERGROUPLIST where GROUPID not in (select GROUPID from USERGROUPLIST where USERID = 'anant.dubey') group by GROUPID


Tuesday, October 12, 2021

Enable delete button on Purchase order or change Purchase order status or delete purchase order versions in d365


To change that status to draft write following code where we will change its status to draft and further code to is to remove version of purchase order which necessary to make delete button enabled on purchase order form


code:-


/// <summary>
/// extension of class: ReqTransPoMarkFirm
/// </summary>
[ExtensionOf(classStr(ReqTransPoMarkFirm))]
final class ReqTransPoMarkFirmCFSClass_Extension
{
    public container conPurchOrders;

    /// <summary>
    /// updatePurchTable
    /// </summary>
    /// <param name = “_purchTable”>_purchTable</param>
    protected void updatePurchTable(PurchTable _purchTable)
    {
        conPurchOrders += _purchTable.PurchId;

        next updatePurchTable(_purchTable);
    }

    /// <summary>
    /// purchTablePostProcessing
    /// </summary>
    protected void purchTablePostProcessing()
    {
        next purchTablePostProcessing();

        for (int i = 1; i <= conLen(conPurchOrders); i++)
        {
            PurchTable purchTable = PurchTable::find(conPeek(conPurchOrders, i), true);

            if(purchTable.RecId)
            {
                ttsbegin;
                //delete the version created for po
                PurchTableVersion purchTableVersion = PurchTableVersion::findLatest(purchTable.PurchId, purchTable.DataAreaId, true);

                if(purchTableVersion.RecId)
                {
                    purchTableVersion.delete();
                }

                purchTable.ChangeRequestRequired = NoYes::No;
                purchTable.DocumentState         = VersioningDocumentState::Draft;
                purchTable.update();

                ttscommit;
            }
        }

    }


Ref: - https://www.cloudfronts.com/change-planned-purchase-order-status-as-draft-insted-of-default-approved/ 

Add link on infolog message in D365 fo

 class CFSMessageAPI

{
    public static void main(Args _args)
    {
        SalesTable            salesTable = SalesTable::find('SH-000121');
        MenuItemMessageAction actionData = new MenuItemMessageAction();

        actionData.MenuItemName(menuItemDisplayStr(SalesTable));
        actionData.TableName(tableStr(SalesTable));
        actionData.RecId(salesTable.RecId);
        str jsonData = FormJsonSerializer::serializeClass(actionData);

        int64 messageId = Message::AddAction(MessageSeverity::Informational, "Sales order details", salesTable.customerName(), MessageActionType::DisplayMenuItem, jsonData);
    }

}
Ref: - https://www.cloudfronts.com/message-api-messageaddaction-in-d365fo-version-10-0-10-pu34-new-feature/

Add FactBox in D365 fo

Ref: - https://www.cloudfronts.com/factbox-of-workflow-history-on-purchase-order-in-d365fo/ 

Details: 

Well, Factbox is a very pretty cool feature available from the earlier version AX 2012. It is very easy to achieve in Dynamics 365 as well. 

Here we will see how easy it is and steps for that. 







 

















As shown in the above image, we will be going to create factbox for the “Tracking details list”

Steps: 

Create the solution in Visual Studio and project for that. We’ll be required four objects mainly that is one display MenuItem, Table, Form (on which to display factbox), another Form (factbox). Reference attached below:

1. Table

For displaying workflow history, we need to create the extension of table WorkflowTrackingStatusTable and create the normal relation and its relevant properties as

2. Factbox

Now we are required to create the factbox of workflow history. For that we will use the effortless technique, where we will duplicate the standard form “WorkflowStatus” and will name as CFSPurchTableWorkflowHistoryFactBox (give name as per your naming convention standards)

After duplicating and renaming the form, we need to change the pattern as “custom”



After applying the custom pattern, delete the NavigationList which is not required in factbox, and do visible false ActionPane and PanelTab

After that create the new Grid and assign the data source to it in the property and put all the required fields to show in factbox

3. Display MenuItem

We will attach the created factbox form to the display menu item and relevant label “Workflow history”

4. Base form

Base form we call it as where we will attach the factbox. Here it is purchase form. Go to Parm section and create a new part and give it a relevant name and its properties

Properties: 

Data source: Attached data source with which workflow history is connected
Data Source Relation: WorkflowTable.RelationName
Name: Part name

That’s it. Build the project and go the frontend and check the output how it looks like.

Conclusion:

It results as (we can scroll left and right to see all the tracking details list)

Hurray, How pretty it looks like!

Synchronize DB using power shell in d365 fo

 

K:\AOSService\webroot\bin\Microsoft.Dynamics.AX.Deployment.Setup.exe 
-bindir "K:\AosService\PackagesLocalDirectory" 
metadatadir "K:\AosService\PackagesLocalDirectory" 
-sqluser "axdbadmin" -sqlserver "." -sqldatabase "AxDB" 
-setupmode "sync" -syncmode "fullall" 
-isazuresql "false" -sqlpwd "AOSWebSite@123" 
-logfilename "H:\MSSQL_LOGS\AxDB_log.log"
Ref: - https://www.cloudfronts.com/d365-finance-and-operations-database-synchronization-using-powershell/

Monday, October 11, 2021

Hide Dialog field from data contract in d365

 


[DataMemberAttribute,SysOperationControlVisibilityAttribute(false)]
public ItemId parmItemId(ItemId _itemId = itemId)
{
    ;

    itemId = _itemId;
    return itemId;
}
Ref:- https://www.miklix.com/dynamics-ax/hide-sysoperation-data-contract-dialog-field-in-dynamics-ax-2012/


Sunday, October 10, 2021

Data contract Class parmQuery method in d365 fo

 First, in the data contract class, the query will be stored packed in a string. Its parm method must be decorated with the AifQueryTypeAttribute attribute, like so (in this example I’ve used the SalesUpdate query, but you can replace this with any AOT query):

[   
    DataMemberAttribute,
    AifQueryTypeAttribute('_packedQuery', queryStr(SalesUpdate))
]
public str parmPackedQuery(str _packedQuery = packedQuery)
{
    ;

    packedQuery = _packedQuery;
    return packedQuery;
}

If you want the query to be decided by the controller class instead, you can also use an empty string. In that case, you also need to implement a couple of helper methods (which you probably should implement anyway for your own convenience when you need to access the query):

public Query getQuery()
{
    ;

    return new Query(SysOperationHelper::base64Decode(packedQuery)); 
}
public void setQuery(Query _query)
{
    ;

    packedQuery = SysOperationHelper::base64Encode(_query.pack());
}

If you need to initialize the query (for example, add ranges), you should implement an initQuery method

public void initQuery()
{
    Query queryLocal = this.getQuery();
    ;

    // add ranges, etc...

    this.setQuery(queryLocal);
}

You need to make sure to call this method from the controller class.


Ref: - https://www.miklix.com/dynamics-ax/using-a-query-in-a-sysoperation-data-contract-class-in-dynamics-ax-2012/

Thanks Mikkel

Convert Real to string in d365 fo

 strFmt() is enough, but that function always rounds off to two decimals, which is not always what I want.

Then there’s the num2str() function, which does work well, but requires you know ahead of time how many decimals and characters you want.

What if you just want the number converted to a string, with all digits and decimals? For some reason, this is something that always has me Googling because it is surprisingly obscure to do and I do it so rarely that I usually can’t remember exactly how – in most languages, I would expect that you could just concatenate the real to an empty string, but X++ doesn’t support that.

Anyway, the by far easiest way I have found to do this is by using a .NET call. There are multiple options here as well, with and without options for advanced formatting, but if you just want the really simple conversion of a real to a string, this will suffice:

stringValue = System.Convert::ToString(realValue);

If this code is to be run on the AOS (for example in a batch job), remember to assert the necessary code access permission first. In this case you’ll need an InteropPermission of type ClrInterop to call .NET code, so the full code example would look something like this:

new InteropPermission(InteropKind::ClrInterop).assert();
stringValue = System.Convert::ToString(realValue);
CodeAccessPermission::revertAssert();

Be aware that this simple System::Convert function uses the system’s current locale with respect to decimal point character. This may not be an issue for you, but for me who lives in an area where comma is used rather than period as decimal separator, it may require further processing if the string for example needs to be used in a file that must be readable by other systems.


Ref: - https://www.miklix.com/dynamics-ax/convert-a-real-to-string-with-all-decimals-in-dynamics-ax-2012/

Thanks Mikkel

Get All Enum Values in d365

 DictEnum dictEnum = new DictEnum(enumNum(SalesStatus));

Counter  c;
;

for (c = 0; c < dictEnum.values(); c++)
{
    info(strFmt('%1: %2', dictEnum.index2Symbol(c),
                          dictEnum.index2Label(c)));
}

This will output the symbol and label of each element in the enum to the infolog.


Ref: - https://www.miklix.com/dynamics-ax/how-to-iterate-over-the-elements-of-an-enum-from-x-code-in-dynamics-ax-2012/

Tuesday, September 21, 2021

Convert Amount in words in d365 India

 static void amount2words(Args _args)

{
real amount = 1011.05;
int numOfPennies = frac(amount)*100 mod 100;
int test = real2int(round(amount,0));
int paise;
int numOfTenths;
str 20 ones[19], tenths[9], hundreds, thousands, lakhs, crores, millions, billions;
str 40 textpaise;
int tmpnumofpennies;
int temp;
str 200 returntxt;

int checkPower(int _test, int _power)
{
int numOfPower;

if (_test >= _power)
{
numOfPower = _test DIV _power;
if (numOfPower >= 100)
{
temp = numOfPower DIV 100;
returntxt = returntxt + ‘ ‘ + ones[temp] + ‘ ‘ + hundreds;
numOfPower = numOfPower MOD 100;
}
if (numOfPower >= 20)
{
temp = numOfPower DIV 10;
returntxt = returntxt + ‘ ‘ + tenths[temp];
numOfPower = numOfPower MOD 10;
}
if (numOfPower >= 1)
{
returntxt = returntxt + ‘ ‘ + ones[numOfPower];
numOfPower = numOfPower MOD 10;
}
switch(_power)
{
case 1000000000 :
{
returntxt = returntxt + ‘ ‘ + billions;
_test = _test MOD 1000000000;
break;
}
case 10000000 :
{
returntxt = returntxt + ‘ ‘ + crores;
_test = _test MOD 10000000;
break;
}
case 100000 :
{
returntxt = returntxt + ‘ ‘ + lakhs;
_test = _test MOD 100000;
break;
}
case 1000 :
{
returntxt = returntxt + ‘ ‘ + thousands;
_test = _test MOD 1000;
break;
}
case 100 :
{
returntxt = returntxt + ‘ ‘ + hundreds;
_test = _test MOD 100;
break;
}
}
}
return _test;
}

ones[1] = “@SYS26620”;
ones[2] = “@SYS26621”;
ones[3] = “@SYS26622”;
ones[4] = “@SYS26626”;
ones[5] = “@SYS26627”;
ones[6] = “@SYS26628”;
ones[7] = “@SYS26629”;
ones[8] = “@SYS26630”;
ones[9] = “@SYS26631”;
ones[10] = “@SYS26632”;
ones[11] = “@SYS26633”;
ones[12] = “@SYS26634”;
ones[13] = “@SYS26635”;
ones[14] = “@SYS26636”;
ones[15] = “@SYS26637”;
ones[16] = “@SYS26638”;
ones[17] = “@SYS26639”;
ones[18] = “@SYS26640”;
ones[19] = “@SYS26641”;

tenths[1] = ‘Not used’;
tenths[2] = “@SYS26643”;
tenths[3] = “@SYS26644”;
tenths[4] = “@SYS26645”;
tenths[5] = “@SYS26646”;
tenths[6] = “@SYS26647”;
tenths[7] = “@SYS26648”;
tenths[8] = “@SYS26649”;
tenths[9] = “@SYS26650”;

hundreds = “@SYS26651”;
thousands = “@SYS26652”;
lakhs = “Lakh”;
crores = “Crore”;
millions = “@SYS26653”;
billions = “@SYS26654”;
test = checkPower(test, 1000000000);
test = checkPower(test, 10000000);
test = checkPower(test, 100000);
test = checkPower(test, 1000);
test = checkPower(test, 100);

if (test >= 20)
{
numOfTenths = test DIV 10;
returntxt = returntxt + ‘ ‘ + tenths[numofTenths];
numOfTenths = numOfTenths MOD 10;
test = test MOD 10;
}
if (test >= 1)
{
numOfTenths = test;
returntxt = returntxt + ‘ ‘ + ones[numOfTenths];
}
if (numOfPennies)
{
returntxt = returntxt + ‘ ‘ + “@SYS5534” + “\n\n”;
}
paise=numofPennies;
if(paise)
{
if(paise >=20)
{
numofTenths= paise DIV 10;
returntxt=returntxt + ‘ ‘+tenths[numofTenths];
numofTenths = numofTenths MOD 10;
paise=paise MOD 10;
}
if(paise >=1)
{
numOfTenths=paise;
returntxt=returntxt+ ‘ ‘+ones[numOfTenths];
}
returntxt=returntxt+” Paise”;
}

info(strFmt(“%1″,returntxt + ” Only”));
}

Sunday, September 19, 2021

Get Value from Multi-Select Control Or Get split value from a string in d365


public container SSI_createContainers()
{
List            list = new List(Types::String);
ListIterator    i;
    container       RecIds, Names;
RecId           id;

//Create Container for the RecIds, Names
list = Global::strSplit(SalesParameters.SSI_MainAccountId,”;”);

i = new ListIterator(list);
while(i.more())
{
id = MainAccount::findByMainAccountId(i.value()).RecId;
RecIds += id;
Names += i.value();
i.next();
}

return [RecIds, Names];

}


reference - https://stoneridgesoftware.com/how-to-create-multi-select-lookup-in-microsoft-dynamics-ax/


Link for multi select lookup on SSRS report - 

https://dynamicsaxinsight.wordpress.com/2014/12/12/ax-2012-multi-select-lookup-for-ssrs-report-dialog/


Thursday, September 16, 2021

Testing

 

private void AFZLookupDimension(FormStringControl _formStringControl, Name _dimensionName) { DimensionAttribute DimensionAttribute = DimensionAttribute::findByName(_dimensionName); Query query = new Query(); QueryBuildDataSource qbdsFinancialTag,qbdsDirCategory; QueryBuildRange qbr; SysTableLookup SysTableLookup = SysTableLookup::newParameters(tableNum(DimensionFinancialTag),_formStringControl); qbdsFinancialTag = query.addDataSource(tableNum(DimensionFinancialTag)); qbdsFinancialTag.addOrderByField(fieldNum(DimensionFinancialTag,Value),SortOrder::Ascending); qbdsDirCategory = qbdsFinancialTag.addDataSource(tableNum(DimensionAttributeDirCategory)); qbdsDirCategory.relations(false); qbdsDirCategory.addLink(fieldNum(DimensionFinancialTag,FinancialTagCategory),fieldNum(DimensionAttributeDirCategory,DirCategory)); qbr = qbdsDirCategory.addRange(fieldNum(DimensionAttributeDirCategory,DimensionAttribute)); qbr.value(queryValue(DimensionAttribute.RecId)); SysTableLookup.addLookupfield(fieldNum(DimensionFinancialTag,Value)); SysTableLookup.addLookupfield(fieldNum(DimensionFinancialTag,Description)); SysTableLookup.addSelectionField(fieldNum(DimensionFinancialTag,Value)); SysTableLookup.parmQuery(query); SysTableLookup.performFormLookup(); }

Wednesday, September 15, 2021

Get Ledger Dimension in based on different values in d365

<> 

class AFZGlobalClass

{

    public static DimensionDynamicAccount   generateLedgerDimension(container    _conData, MainAccountNum _mainAccountId)

    {

        int hierarchyCount;

        int hierarchyIdx;

        RecId                   dimAttId_MainAccount;

        LedgerRecId            ledgerRecId;

        MainAccount mainAccount;

        RefRecId recordvalue;

        DimensionAttribute     dimensionAttribute;

        DimensionAttributeValue dimensionAttributeValue;

        DimensionSetSegmentName DimensionSet;

        DimensionStorage         dimStorage;

        LedgerAccountContract LedgerAccountContract = new LedgerAccountContract();

        DimensionAttributeValueContract  ValueContract;

        List   valueContracts = new List(Types::Class);

        dimensionAttributeValueCombination dimensionAttributeValueCombination;

        //CostCenter,Department,Worker ..etc..

        //container                  _conData =["142102","C","ADMIN"];;

        //mainAccount = MainAccount::findByMainAccountId('400301');

        mainAccount = MainAccount::findByMainAccountId(_mainAccountId);

        recordvalue = DimensionHierarchy::getAccountStructure(mainAccount.RecId,Ledger::current());

        hierarchyCount = DimensionHierarchy::getLevelCount(recordvalue);

        DimensionSet = DimensionHierarchyLevel::getDimensionHierarchyLevelNames(recordvalue);

        for(hierarchyIdx = 1;hierarchyIdx<=hierarchyCount;hierarchyIdx++)

        {

            if(hierarchyIdx == 1)

           continue;

            dimensionAttribute = DimensionAttribute::findByLocalizedName(DimensionSet[hierarchyIdx],false,"en-us");

            if(dimensionAttribute)

            {

                dimensionAttributeValue = DimensionAttributeValue::findByDimensionAttributeAndValue(dimensionAttribute,conPeek(_conData,hierarchyIdx));


                if(dimensionAttributeValue)

                {

                    ValueContract = new DimensionAttributeValueContract();

                    ValueContract.parmName(dimensionAttribute.Name) ;

                    ValueContract.parmValue(dimensionAttributeValue.CachedDisplayValue);

                    valueContracts.addEnd(ValueContract);

                }

            }

        }

        LedgerAccountContract.parmMainAccount(_mainAccountId);

        LedgerAccountContract.parmValues(valueContracts);

        dimStorage = DimensionServiceProvider::buildDimensionStorageForLedgerAccount(LedgerAccountContract);

        dimensionAttributeValueCombination = DimensionAttributeValueCombination::find(dimStorage.save());

        ledgerRecId = dimensionAttributeValueCombination.RecId;

        return ledgerRecId;

    }

}

</>


Reference link - https://www.linkedin.com/pulse/generate-financial-dimension-ledgerdimension-dynamics-yadav/
Thanks - Shashi kant Yadav

Wednesday, September 1, 2021

Deploy Package using RunBook using command prompt in d365

 

AXUpdateInstaller.exe generate -runbookid="Package-00130-runbook" -topologyfile="DefaultTopologyData.xml" -servicemodelfile="DefaultServiceModelData.xml" -runbookfile="Package-00130-runbook.xml"

AXUpdateInstaller.exe import -runbookfile=Package-00130-runbook.xml

AXUpdateInstaller.exe list

AXUpdateInstaller.exe execute -runbookid=Package-00130-runbook
-----------------------------------------------
above these are main commands to import pkg

link - http://d365technext.blogspot.com/2018/06/package-deployment-using-run-book.html

Deploy model wise All SSRS Reports in d365fo using powershell

For Model wise

C:\AosService\PackagesLocalDirectory\Plugins\AxReportVmRoleStartupTask\DeployAllReportsToSSRS.ps1 -Module MODELNAME -PackageInstallLocation "C:\AosService\PackagesLocalDirectory"


For Local environment

C:\Packages\Plugins\AxReportStartVmRoleStartupTask\DeployAllReportsToSSRS.ps1


For Azure environment

C:\AosService\PackagesLocalDirectory\Plugins\AxReportVmRoleStartupTask\DeployAllReportsToSSRS.ps1 -PackageInstallLocation “C:\AosService\PackagesLocalDirectory”



if required Run below command first - 

Set-ExecutionPolicy Unrestricted

Y

Tuesday, August 31, 2021

How to get value of FormReferenceGroupControl in d365 fo

 


It should be as simple as calling the value() method on the FormReferenceGroupControl object to get the underlying Reference value (int64) which is the RecId of the underlying datasource. For example:

FormReferenceGroupControl referenceGroupControl;

referenceGroupControl = element.control(element.controlId(formControlStr(ReferenceGroupTestingForm, ReviewHeaderInfo_CustomsRepRefGroup))) as FormReferenceGroupControl;

referenceGroupControl.value(); //returns the RecId of the DirPerson table displayed.

To get the Display value which is substituted to the user instead of the underlying RecId value stored in the database, do this:

FormReferenceGroupControl referenceGroupControl;

referenceGroupControl = element.control(element.controlId(formControlStr(ReferenceGroupTestingForm, ReviewHeaderInfo_CustomsRepRefGroup))) as FormReferenceGroupControl;

//this gets the string control that is substituted in for the reference value/recid and displayed to the user. This is the second underlined control in the picture below. This control is determined by the ReferenceGroupControl property "Replacement Field Group" 
//Could be a different type of control than a String control depending on your scenario
FormStringControl subStringControl = referenceGroupControl.controlNum(1) as FormStringControl;

subStringControl.text(); //for string controls, text will contain the display value

enter image description here

One last thing to note is that I believe it is possible to get the values by manipulating datasource objects instead of formcontrol objects. I have seen solutions like this in the past while hunting through google search results.


ref link - https://stackoverflow.com/questions/57508423/how-to-get-value-of-formreferencegroupcontrol