17 February, 2026

Useful DateTimeUtil Methods in D365 FO X++ (Dynamics AX / Dynamics 365 Finance & Operations)

Working with date and time is a very common requirement when developing solutions in Dynamics AX and Dynamics 365 Finance & Operations. Whether it’s handling batch jobs, logging execution time, applying business rules based on dates or converting values across time zones, date-time handling needs to be done carefully.

In X++ DateTimeUtil plays a key role in managing all date and time–related operations. It provides several built-in methods to convert dates, handle UTCDateTime values and work with user or system time zones in a reliable way.

During my development work, I have frequently used the following DateTimeUtil methods. I have collected them here as a quick reference that can help developers understand and use date-time operations more effectively in Dynamics AX / D365 FO.


Below is a list of commonly used DateTimeUtil methods with examples that you can use in your day-to-day X++ development.


// Get Past date time

DateTimeUtil::addDays(DateTimeUtil::utcNow(), -1);


// Convert Date to DateTime 

 FromDateTime fromDateTime = DateTimeUtil::newDateTime(Fromdatefilter.dateValue(), 0);

 ToDateTime toDateTime = DateTimeUtil::newDateTime(ToDateFilter.dateValue(), timeMax());


// Today date
date today = DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone());


//Convert DateTime to Date
DateTimeUtil::date(ledgerJournalTable.PostedDateTime)


//Convert to company date
 public date convertToCompanyDate(utcDateTime _dateTime)

 {

     return HcmDateTimeUtil::convertToCompanyDate(DateTimeUtil::removeTimeZoneOffset(_dateTime, userTimeZone));

 }


//Get min and max DateTime value 

utcdatetime validFromDate = DateTimeUtil::minValue();

utcdatetime validToDate = DateTimeUtil::maxValue();


//Get current year from date

 int defaultYear = year(DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone()));


//Last date in year

Date lastDayInYear = DateTimeUtil::getEndOfYearDate(locale, firstDayInYear);

26 June, 2024

How to Use CrossCompany in a Query in Dynamics 365 F&O (X++)

While working in Dynamics 365 Finance & Operations, there are many situations where we need to read data from multiple legal entities instead of just the current company.

By default, any query or select statement in X++ only fetches records from the company you are logged into. But what if you want data from all companies?

That’s where CrossCompany comes into the picture. 

What Is CrossCompany in D365 F&O?

CrossCompany allows you to read records from multiple legal entities in a single query without changing the company context.

Normally, D365 F&O automatically applies a DataAreaId filter behind the scenes. When CrossCompany is enabled, this filter is ignored and data is returned from all companies that the user has access to.


Example: Using CrossCompany in a Query

Below is a simple example where we want to read InventLocation records from all companies.



Query query = new Query();

QueryBuildDataSource queryBuildDataSource;

QueryBuildRange queryBuildRange;

queryBuildDataSource = query.addDataSource(tableNum(InventLocation));

query.allowCrossCompany(true);

How to cancel sales line in D365 F&O using X++

Sometimes in D365 Finance & Operations, we run into situations where we need to cancel a sales order line through code instead of manually doing it from the form. This usually comes up in automation scenarios, integrations or custom business processes.

Recently I had a requirement where a sales line needed to be cancelled using code. Instead of directly updating fields, I used the standard framework logic. In this post, I will share a simple and clean way to do it.

Implementation : Cancelling a Sales Line

In my case, I created a table extension for SalesLine and added a custom method that can be called whenever we need to cancel a specific line.

[ExtensionOf(tableStr(SalesLine))]
final class SalesLine_Extension
{
    public void gauCancelSalesOrderLine()
    {
        ttsbegin;
        this.selectForUpdate(true);
        SalesUpdateRemain::construct().updateDeliverRemainder(this, 0, 0);
        ttscommit;
    }
}

How to call Runbase batch job using X++ in D365 F&O

If there's a requirement to call a RunBaseBatch class using X++ code, the following example demonstrates how to do it. In this case, we’re using the GUPItemBasePriceCalcJob class, which is a RunBaseBatch class and calling it by setting the appropriate parameters.        

        BatchHeader         header;

        SysRecurrenceData   sysRecurrenceData;

        Batch               batch;

        BatchJob            batchJob;

        BatchInfo           processBatchInfo;

        BatchRetries        noOfRetriesOnFailure = 4;

        GUPItemBasePriceCalcJob GUPItemBasePriceCalcJob; // RunBase batch class

        #define.timeInSecondsDelay(20)

      

        select forupdate batch

        join batchJob

        where batchJob.RecId == batch.BatchJobId

                && batch.ClassNumber == classnum(GUPItemBasePriceCalcJob)

                && batchJob.Status == BatchStatus::Waiting

                && batch.Company == curext();

        if (!batch)

        {

            // Setup the RunBaseBatch Job

            header = BatchHeader::construct();

            GUPItemBasePriceCalcJob= GUPItemBasePriceCalcJob::construct();

            GUPItemBasePriceCalcJob.parmItemId(ItemId); // pass Item Id 

            GUPItemBasePriceCalcJob.parmSiteId(SiteId); // pass Site 

            processBatchInfo = GUPItemBasePriceCalcJob.batchInfo();

            processBatchInfo.parmRetriesOnFailure(noOfRetriesOnFailure);

            header.addTask(GUPItemBasePriceCalcJob);            


            // Set the recurrence data

            sysRecurrenceData = SysRecurrence::defaultRecurrence();

            sysRecurrenceData = SysRecurrence::setRecurrenceStartDateTime(sysRecurrenceData,                             DateTimeUtil::addSeconds(DateTimeUtil::utcNow(), #timeInSecondsDelay));

            sysRecurrenceData = SysRecurrence::setRecurrenceNoEnd(sysRecurrenceData);

            sysRecurrenceData = SysRecurrence::setRecurrenceEndAfter(sysRecurrenceData, 1); 


            header.parmRecurrenceData(sysRecurrenceData);

            // Set the batch alert configurations

            header.parmAlerts(NoYes::No, NoYes::Yes, NoYes::No, NoYes::Yes, NoYes::No);

            header.save();

14 May, 2024

How to call SysOperation batch job using X++ in D365 F&O

While working on a recent requirement, I needed to trigger a SysOperation framework class directly from code and run it in batch without showing the dialog to the user.

Instead of manually opening the dialog or relying on menu items, I used the controller class to programmatically set the contract values and execute the process in scheduled batch mode. In this post, I will walk through the approach.

Example - 1st 

We create the controller object to initialize the SysOperation framework.

The contract is retrieved and required parameters like ItemId and InventSiteId are set for processing.

The execution mode is set to batch and the dialog is disabled to run the process in the background.


Finally startOperation() triggers the process with all the configured settings.

Code - 

ChangeItemController     controller  = ChangeItemController ::construct();
ChangeItemContract       contract    = controller ? controller.getDataContractObject() : null;
if  (!contract)
{
        throw error(Error::wrongUseOfFunction(funcName()));
}
// Set contract fields
contract.parmItemId(this.ItemId);
contract.parmInventSiteId(this.SiteId );
controller.parmExecutionMode(SysOperationExecutionMode::ScheduledBatch); // Set execution mode
controller.parmShowDialog(false); 
controller.parmLoadFromSysLastValue(false); 
controller.parmInBatch(true);
controller.parmDialogCaption('Change Item');
controller.startOperation();


Example - 2nd

This code programmatically runs the Invent On-hand Storage process in batch mode. It first builds a query filtered by Site and Warehouse using the InventDim table. The query is then encoded and passed to the InventOnhandStorageController through its data contract. The controller is configured to run as a scheduled batch job, without showing the dialog or using last saved values, and finally starts the operation.


Code - 


//Build the query Query query = new Query(queryStr(InventOnhand)); 

QueryBuildDataSource qbds = query.dataSourceTable(tableNum(InventDim)); 

QueryBuildRange qbrWH  = qbds.addRange(fieldNum(InventDim, InventLocationId)); QueryBuildRange qbrSite  = qbds.addRange(fieldNum(InventDim, InventSiteId)); qbrWH.value('WH');

qbrSite.value('SITE'); 


// Create Args object Args args = new Args(); args.name(menuItemActionStr(InventOnhandStorage)); 

args.caller(this); 

InventOnhandStorageController controller = InventOnhandStorageController::construct(args); InventOnhandStorageContract contract    = controller ? controller.getDataContractObject() : null; 


if (!contract) 

    throw error(Error::wrongUseOfFunction(funcName())); 

}


controller.parmInBatch(true); controller.parmExecutionMode(SysOperationExecutionMode::ScheduledBatch); contract.parmViewInventSiteId(true);

contract.parmViewInventLocationId(true); contract.parmQueryString(SysOperationHelper::base64Encode(query.pack())); 

controller.parmShowDialog(false); controller.parmLoadFromSysLastValue(false); controller.batchInfo().parmCaption("@SYS97724"); 

controller.startOperation();

27 April, 2024

Create ledger journals in D365FO using X++

While working on Dynamics 365 Finance & Operations, there are many situations where we need to create ledger journals through X++ code instead of entering them manually from the UI.

This usually comes up when data is coming from an external system, a staging table, or when we want to automate accounting entries.

In this post, I’m sharing a simple approach I used to create ledger journals and journal lines programmatically using X++.

High-Level Approach

The process is straightforward:

  1. Create the journal header

  2. Initialize LedgerJournalEngine

  3. Create journal lines

  4. Set account, dimensions, debit/credit

  5. Insert the records

Step 1: Create Journal Header

// Header creation 

ledgerJournalTable.JournalNum = NumberSeq::newGetNum(LedgerParameters::numRefJournalNum()).num();

ledgerJournalTable.initFromLedgerJournalName(ledgerJournalTrans_Buffer.JournalName);

ledgerJournalTable.insert();

ledgerJournalEngine = LedgerJournalEngine::construct(ledgerJournalTable.JournalType);

ledgerJournalEngine.ledgerJournalTable(ledgerJournalTable);

ledgerJournalEngine.newJournalActive(ledgerJournalTable);


Step 2: Prepare for Journal Line Creation

//Line creation

ledgerJournalEngine.preCreate(ledgerJournalTrans);

ledgerJournalTrans.clear();

ledgerJournalTrans.initValue();

ledgerJournalTrans.JournalNum   = ledgerJournalTable.JournalNum;

ledgerJournalTrans.TransDate    = stagingTable.TransactionDate;

ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, TransDate));

ledgerJournalTrans.Voucher          =   stagingTable.Voucher;

ledgerJournalTrans.CurrencyCode = stagingTable.CurrencyCode;

ledgerJournalTrans.Due  = stagingTable.DueDate;

ledgerJournalTrans.DocumentDate = stagingTable.DocumentDate;

ledgerJournalTrans.ExchRate = stagingTable.ExchRate;

ledgerJournalTrans.ReportingCurrencyExchRate = stagingTable.ExchRate;

ledgerJournalTrans.Txt  = stagingTable.Description;

ledgerJournalTrans.AccountType = str2Enum(ledgerJournalACType,stagingTable.AccountTypeStr);//stagingTable.AccountType;

ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, AccountType));

if (stagingTable.AccountTypeStr == "Ledger")

{

ledgerJournalTrans.LedgerDimension  = this.generateLedgerDimension(stagingTable.AccountDisplayValue,stagingTable.BU,'',stagingTable.BranchCode,stagingTable.EmployeeCode);

}

else if (stagingTable.AccountTypeStr == "Vendor")

{

ledgerJournalTrans.LedgerDimension  = LedgerDynamicAccountHelper::getDynamicAccountFromAccountNumber(stagingTable.AccountDisplayValue,LedgerJournalACType::Vend);// stagingTable.Account;

ledgerJournalTrans.PostingProfile   = vendParameters.PostingProfile;

}

else if (stagingTable.AccountTypeStr == "Customer")

{

ledgerJournalTrans.LedgerDimension  = LedgerDynamicAccountHelper::getDynamicAccountFromAccountNumber(stagingTable.AccountDisplayValue,LedgerJournalACType::Cust);// stagingTable.Account;

ledgerJournalTrans.PostingProfile   = custParameters.PostingProfile;

}

else if (stagingTable.AccountTypeStr == "Bank")

{

ledgerJournalTrans.LedgerDimension  = LedgerDynamicAccountHelper::getDynamicAccountFromAccountNumber(stagingTable.AccountDisplayValue,LedgerJournalACType::Bank);// stagingTable.Account;

}

ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, LedgerDimension));

ledgerJournalTrans.DefaultDimension = this.createDefaultDimension( stagingTable.LobCode,stagingTable.Department,stagingTable.BranchCode);

if (stagingTable.Credit)

{

ledgerJournalTrans.AmountCurCredit   = stagingTable.Credit;

ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, AmountCurCredit));

}

else if (stagingTable.Debit)

{

ledgerJournalTrans.AmountCurDebit    = stagingTable.Debit;

ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, AmountCurDebit));

}         

ledgerJournalTrans.OffsetAccountType      = LedgerJournalACType::Ledger;

ledgerJournalTrans.modifiedField(fieldNum(LedgerJournalTrans, OffsetAccountType));         

ledgerJournalTrans.insert();


How to create LedgerDimension in D365 F&O using x++

 The code below will help out to generate Ledger Dimension using x++ in D365 FO

   


 public RefRecId  generateLedgerDimension(MainAccountNum     _mainAccountId,

                                             DimensionValue     _businessUnit, 

                                             DimensionValue     _department,

                                             DimensionValue     _branchCode,

                                             DimensionValue     _employeeCode)

    {

        container                           conData;

        int                                 hierarchyCount;

        int                                 hierarchyIdx;

        LedgerRecId                         ledgerRecId;

        MainAccount                         mainAccount;

        RefRecId                            recordvalue;

        DimensionAttribute                  dimensionAttribute;

        DimensionAttributeValue             dimensionAttributeValue;

        DimensionSetSegmentName             DimensionSet;

        DimensionStorage                    dimStorage;

        DimensionAttributeValueContract     ValueContract;

        LedgerAccountContract               LedgerAccountContract;

        DimensionAttributeValueCombination  dimensionAttributeValueCombination;

        List                                valueContracts;


        #define.MainAccount('MainAccount')

        #define.Department('Department')

        #define.BusinessUnit('BusinessUnit')

        #define.BranchCode('BranchCode')

        #define.EmployeeCode('EmployeeCode')

        



        LedgerAccountContract   = new LedgerAccountContract();

        valueContracts          = new List(Types::Class);


        conData = [_mainAccountId];



        mainAccount     =   MainAccount::findByMainAccountId(_mainAccountId);

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

        hierarchyCount  =   DimensionHierarchy::getLevelCount(recordvalue);

        DimensionSet    =   DimensionHierarchyLevel::getDimensionHierarchyLevelNames(recordvalue);


        if (recordvalue)

        {

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

            {

                if(hierarchyIdx == 1)

                {

                    continue;

                }

                dimensionAttribute = DimensionAttribute::findByLocalizedName(DimensionSet[hierarchyIdx],false);

                                

                if (dimensionAttribute.Name == #Department)

                {

                    conData += [_department];

                }

                else if (dimensionAttribute.Name == #BusinessUnit)

                {

                    conData += [_businessUnit];

                }

                else if (dimensionAttribute.Name == #BranchCode)

                {

                    conData += [_branchCode];

                }

                else if (dimensionAttribute.Name == #EmployeeCode)

                {

                    conData += [_employeeCode];

                }



                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;

        }

        else

        {

            warning (strfmt('Ledger dimension Issue for main account %1', _mainAccountId));

        }


        return  ledgerRecId;

    }

03 April, 2023

CostCenter lookup in Dynamics 365 for finance and operations


The code below will help to get cost center lookup for a field in D365 FO using X++.

Lets say our requirement is to show all values of "CostCentre" financial dimension. 

LedgerMatrixReportHelper_CN::dimensionValueLookup(this, "CostCenter");

24 April, 2020

Parameter display order in report dialog in Dynamics 365 for finance and operations

If you want to put data contract parameters/dialog in an order, you just need to add SysOperationDisplayOrderAttribute attribute to the parm method.

Below is the code snippet.

[DataMemberAttribute, SysOperationDisplayOrderAttribute("2")]
public str parmDocNum(str _docNum = docNum)
{
       docNum = _docNum;
       return docNum;
}

[DataMemberAttribute,SysOperationDisplayOrderAttribute("3")]
public str parmDocVersion(str _docVersion = docVersion)
{
       docVersion = _docVersion;
       return docVersion;
}

Hide parameters in report dialog in D365 FO | Dynamics 365 for finance and operations | D365 F&O

Sometime we get requirement to hide contract parameters either in SSRS reports or SysOperation.

To achieve the same, you need to add SysOperationControlVisibilityAttribute attribute to the parm method of contract class.
Below is the code snippet.

[DataMemberAttribute,SysOperationControlVisibilityAttribute(false)]
 public TableId parmTableId(TableId _tableId = tableId)
 {
     tableId = _tableId;
     return tableId;
 }

25 February, 2018

How to create Details Master pattern form in D365 F&O


Objective :The objective of this tutorial is to take you through the development of details master form in Dynamics 365 for operations.

Development :

  • Create a project, Right-click your project, select Add -> New Item..
  • Select User Interface -> Form,  give some name, in my case its Gau_DistributorTable

  • Drag and drop your table to Data Sources node

  •  Right-click Design node, select Apply pattern -> Details Master

  •         After selecting pattern, we can see all the missing controls in Pattern pane

  • Add all missing controls to design, even after adding controls still some warming is showing up

  • .       Now add all the controls for which we are getting warnings and need to add field group to Detail tab Page from data source

  • .       Add some fields to Grid of Grid tab page

  • .       Right-click form and select Set as Startup Object

  • Select your project and hit Start, here you go
     
THANK YOU





25 January, 2018

Data Entities error while uploading File in Dynamics 365 F&O

Error: File upload for entity in data project did not succeed

While importing data to Dynamics 365 for Operations you may encounter an error if Azure Storage Emulator is not up.




Solution:-

You will have to start Azure storage emulator .

About storage emulator :
The storage emulator uses a local Microsoft SQL Server instance and the local file system to emulate Azure storage services. 
The storage emulator is installed by default to -

 C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator.

How to start Azure storage emulator :
  • Run command Prompt as Administrator
  • Copy paste this path to cmd – cd C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator
  •      Execute this command on cmd- AzureStorageEmulator.exe start



THANK YOU


22 May, 2017

X++ new features in D365 F&O

DECLARE ANYWHERE:-  Previously, all local variables had to be placed at the start of the method in which they’re used. Readability is enhanced. You can reduce the risk of reusing a variable inappropriately during long-term maintenance of the code.

Static Constructor and Static field:- Static constructors are guaranteed to run before any static or instance calls are made to the class.

Const/ReadOnly:-  the const is known by IntelliSense, The const is subject to access modifiers, either private, protected, or public. The accessibility of macros is not well understood or even rigorously defined. Consts have scope, while macros do not

Var:-  It’s only possible to use var on declarations where an initialization expressions are provided

Private and Protected member varaibles:- Previously, all member variables defined in a class were invariably protected. It’s now possible to make the visibility of member variables explicit by adding the private, protected, and public keywords.

Extension method:- The extension class must be static, The name of the extension class must end with the ten-character suffix _Extension. However, there’s no restriction on the part of the name that precedes the suffix, Every extension method in the extension class must be declared as public static, the first parameter in every extension method is the type that the extension method extends. However, when the extension method is called, the caller must not pass in anything for the first parameter. Instead, the system automatically passes in the required object for the first parameter. 

Finally in try/catch statements :- Try/catch statements can now include an optional finally clause. The statements in the finally clause are executed when control leaves the try block, either normally or through an exception.