Tuesday, August 18, 2026

X++ Code to pick the Batch error logs in D365F&O

 My requirement is to pick the error logs for particular batch jobs.





Click on the view tasks to check the execution log details. I need to get the below error details in custom form.


Code :

This code picks the latest executed batch record. you can apply conditions based on the requirement.

BatchJobHistory    batchJobHistory;

   select batchJobHistory
       order by batchJobHistory.RecId desc
       where batchJobHistory.BatchJobId == "5638569578";          
   
   if(batchJobHistory.Status == BatchStatus::Error)
   {                    

       BatchHistory            batchHistory;
       BatchJobHistory         batchJobHistoryloc;
       SysInfologEnumerator    enumerator;

       while select RecId, Info, Caption from batchHistory where 
           batchHistory.BatchJobHistoryId == batchJobHistory.RecId
           && batchHistory.Status == BatchStatus::Error
       {
           str                             ret;
           enumerator = SysInfologEnumerator::newData(batchHistory.Info);

           while (enumerator.moveNext())
           {
               if (enumerator.currentException() == Exception::Error)
               {
                   ret += enumerator.currentMessage() + '\n';
               }
           }
            errorBatchJobTable.Log              =   ret; // holds the error.
        }
}

Output :



Note : BatchJobHistory::showlog() method brings the full log for the history. See the message details.






Create and Manage LCS access for new users

 Create an LCS Project

1. Open lcs.dynamics.com in your web browser.
2. Log in with your credentials. (If you do not have credentials, you will either need to create an account or work with your administrator. If you are a Global admin in Office 365 then you can go to office.portal.com and log in, Click Admin to go to the Admin center, then click Admin > Dynamics 365 for Operations. This will take you over to LCS with your credentials that you logged into Office 365 with.)
3. On the LCS home page click the Add icon in the Recent projects area.

4. On the Create a project pane, type a unique name for your project in the Name field. For example if this project will be used for a Demo environment you might type "Demo". If you are a customer and you want a separate project for Developers, for example, you might type "Development".
5. Type a detail description in the Description field.
6. In the Product name field, select Microsoft Dynamics 365 for Operations.
7. In the Industry field, select an appropriate industry or if one of the options does not apply to your organization, select Other.
8. Click Create.


Add a Project User to the LCS Project
The next step in the process is to add users to your project. Users are required for anyone that needs access to manage or monitor your environment. If you are using the Business process library or other features, a user would need access to be able to manage and upload documents, modify the methodology and so on. 
 
1. Open lcs.dynamics.com in your web browser.
2. Log in with your credentials.
3. Select the LCS project you want to add a user to from the list.
4. Click the Hamburger menu button. (That really is the real name for that funny little button with three horizontal lines - the more you know!)
5. Select Project users from the list.

6. On the Project user management page, click the Add icon to create a new user.
7. In the Email field you can do one of two things:
      a. If the user you are adding is part of your organization (a user in your Office 365 tenancy or AAD tenancy) then you can use the User lookup field to find the user.
      b. If the user is not part of your organization, or you just want to type an email address, then enter that email address into the Email field.
8. In the Project role field, select the role you want to assign to the user.
9. Click Invite.



Thursday, March 12, 2026

How to Block Manual Execution of a Running Recurring Batch Job in D365FO (X++)

 public boolean validate()

{

    boolean ret = true;

    BatchJob batchJob;

    Batch batch;


    select firstOnly batchJob

    join batch

    where batch.BatchJobId == batchJob.RecId

       && batch.ClassNumber == className2Id(classStr(Sysopertaioncontrollertest))

       && (batchJob.Status == BatchStatus::Waiting ||

           batchJob.Status == BatchStatus::Executing);

    if (batchJob)

    {

        error("This batch job is already running. Please wait for it to finish.");

        ret = false;

    }

    return ret;

}

Tuesday, February 24, 2026

Code to update Acutal sales tax amount of a salesline in D365F&O X++

public static void main(Args _args)

{

   SalesTable      salesTable = SalesTable::find("000811");

   TaxRegulation   taxRegulation;

   taxRegulation = TaxRegulation::newTaxRegulation(

   SalesTotals::getTax(salesTable),

   null,

   tableNum(SalesLine),

   68719862322); //salesline recid

   if(taxRegulation)

  {

       taxRegulation.allocateAmount(10);

       taxRegulation.saveTaxRegulation();

   }      

}

Output :


SalesLine :



Code to get the Actual sales tax amount for a salesLine in D365F&O X++

public static void main(Args _args)

{

   SalesTotals           salesTotals;

   TaxAmountCur      taxAmountCur;

   SalesTable              salesTable = SalesTable::find("000811");

   salesTotals = SalesTotals::construct(salesTable);

   salesTotals.calc();

   salesTotals.tax().sourceSingleLine(true, true);

   taxAmountCur    =   salesTotals.tax().totalTaxAmountSingleLine(tableNum(SalesLine), 68719676632, true, false); //salesline recid

   Info(strFmt("%1",taxAmountCur));

}

SalesLine>Financials>SalesTax


 

Reference link : https://community.dynamics.com/forums/thread/details/?threadid=3662fb97-0b10-4d8c-b3c4-cd2a4d64018a

Tuesday, February 17, 2026

Extending Standard Budget Validation with Line-Level Legal Entity Customization

This is a customization where I added a custom field at the line level to capture the legal entity. When a specific legal entity is selected in this field, the system triggers the above process to validate the budget. If sufficient budget is available for that legal entity, the transaction proceeds. If not, the standard process is allowed to execute and throw the appropriate error.


[ExtensionOf(classStr(BudgetControlProcessor))]
final class Dax_BudgetControlProcessor_Extension
{

    public str errormsg;
    public boolean result;

    protected boolean abortProcessForBudgetCheckFailure()
    {
        boolean doAbortProcess;
        Ledger primaryLedger;
        BudgetControlConfiguration configuration;
        CompanyInfo  CompanyInfo = CompanyInfo::findDataArea(curExt());
        primaryLedger = Ledger::findByLegalEntity(CompanyInfo.recid);
        if (primaryLedger.IsBudgetControlEnabled)
        {
            configuration = BudgetControlConfiguration::findActiveByPrimaryLedger(primaryLedger.RecId);
        }
        doAbortProcess = next abortProcessForBudgetCheckFailure(); 
        if(configuration)
        {
            doAbortProcess = false;
        }
        return doAbortProcess;

 

    }

 

    public  DimensionDisplayValue getAttributeValueFromCombination(
        DimensionAttributeValueCombination _combination,
        Name _attributeName)
    {
        DimensionAttributeLevelValueView valueView;
        DimensionAttribute attribute = DimensionAttribute::findByName(_attributeName); //Retrive attribute record by Name

        select valueView
            where valueView.ValueCombinationRecId == _combination.recId
&& valueView.DimensionAttribute == attribute.RecId; //Retrieve individual Dimension attribute record

        return valueView.DisplayValue; //Retrive dimension attribute value
    }

 

    public boolean checkBudgetAmount(BudgetSource _budgetSource,BudgetCheckResultErrorWarningDetail _detailResult)
    {
        boolean ret;

 

        BUDGETTRANSACTIONLINE                   bUDGETTRANSACTIONLINE,bUDGETTRANSACTIONLINELoc;
        BUDGETTRANSACTIONHEADER                 bUDGETTRANSACTIONHEADER,bUDGETTRANSACTIONHEADERloc;
        BudgetSource                            budgetSource = _budgetSource;
        BudgetCheckResultErrorWarningDetail     budgetCheckResultErrorWarningDetail;
        DimensionAttributeValueCombination      dimensionAttributeValueCombination,dimensionAttributeValueCombinationloc,dimensionAttributeValueCombinationlocerror;
        DimensionAttribute                      dimensionAttribue,dimensionAttribueloc;
        DimensionAttributeLevelValueAllView     dimensionAttributeLevelValueAllView,dimensionAttributeLevelValueAllViewloc;
        LedgerDimensionBudgetControl            controlLedgerDimension;
        Map map = new Map(Types::String, Types::String);

        MapEnumerator mapEnumerator;
        container                               conDimensions;

 

        DimensionDisplayValue mainAccount,businessUnit,costCenter,legalEntity,mainAccountloc,businessUnitloc,costCenterloc,legalEntityloc,project,projectloc,region,regionloc;

 

        select bUDGETTRANSACTIONLINELoc
            where bUDGETTRANSACTIONLINELoc.RecId == budgetSource.BudgetTransactionLine;

 

        select bUDGETTRANSACTIONHEADERloc
                where bUDGETTRANSACTIONHEADERloc.RecId == bUDGETTRANSACTIONLINELoc.BudgetTransactionHeader;

        if(bUDGETTRANSACTIONHEADERloc.Dax_TransferCrossCompany == NoYes::Yes /*&& bUDGETTRANSACTIONLINELoc.Dax_ToCompany != curExt() */ && bUDGETTRANSACTIONLINELoc.displayAccountingAmount() < 0)
        {
            //LedgerRecId primaryLedgerId = primaryLedgersToCheckBudget.lookup(CompanyInfo::findDataArea(curExt()));

 

// Budget control rule found, get budget control ledger dimension
            controlLedgerDimension = BudgetControlLedgerDimensionHelper::findBudgetControlLedgerDimension(
                                        bUDGETTRANSACTIONHEADERloc.PrimaryLedger,
                                        bUDGETTRANSACTIONLINELoc.LedgerDimension);

 

           // real currentAmount = abs(bUDGETTRANSACTIONLINELoc.displayAccountingAmount());
            real currentAmount = abs(bUDGETTRANSACTIONLINELoc.TransactionCurrencyAmount);

 

            select dimensionAttributeValueCombination
                where dimensionAttributeValueCombination.RecId == bUDGETTRANSACTIONLINELoc.LedgerDimension;

 

            select dimensionAttributeValueCombinationlocerror
                where dimensionAttributeValueCombinationlocerror.RecId == controlLedgerDimension;

 

            while select dimensionAttributeLevelValueAllView
                where dimensionAttributeLevelValueAllView.ValueCombinationRecId == controlLedgerDimension
            {
                DimensionAttribute attribute = DimensionAttribute::find(dimensionAttributeLevelValueAllView.DimensionAttribute);

 

                conDimensions += attribute.Name;
            }

 

            for(int c=1; c<=conLen(conDimensions);c++)
            {
                str dimensionValue = this.getAttributeValueFromCombination(dimensionAttributeValueCombination,conPeek(conDimensions,c));

 

                map.insert(conPeek(conDimensions,c),dimensionValue);
            }
            // mainAccount = LedgerDimensionFacade::getMainAccountFromLedgerDimension(bUDGETTRANSACTIONLINELoc.LedgerDimension).MainAccountId;

 

        
            Amount  budgetAmount;
            boolean  mapResult;

            while select  bUDGETTRANSACTIONLINE
                join bUDGETTRANSACTIONHEADER
                    where bUDGETTRANSACTIONHEADER.RECID == bUDGETTRANSACTIONLINE.BUDGETTRANSACTIONHEADER
&& bUDGETTRANSACTIONHEADER.BUDGETMODELDATAAREAID == bUDGETTRANSACTIONLINELoc.DAX_TOCOMPANY
&& budgetTransactionHeader.TransactionStatus == BudgetTransactionStatus::Completed
            {
                map maploc = new Map(Types::String, Types::String);

 

                select dimensionAttributeValueCombinationloc
                    where dimensionAttributeValueCombinationloc.RecId == bUDGETTRANSACTIONLINE.LedgerDimension;

 

                for(int c=1; c<=conLen(conDimensions);c++)
                {
                    str dimensionValue = this.getAttributeValueFromCombination(dimensionAttributeValueCombinationloc,conPeek(conDimensions,c));

 

                    maploc.insert(conPeek(conDimensions,c),dimensionValue);
                }

                mapResult = Map::equal(map,maploc);

 

                if(mapResult)
                {
                    budgetAmount += bUDGETTRANSACTIONLINE.displayAccountingAmount();
                }
            }

 

            if(budgetAmount == 0)
            {
                errormsg = strFmt("@SYS128612",
                        bUDGETTRANSACTIONHEADERloc.TransactionNumber,
                        dimensionAttributeValueCombinationlocerror.DisplayValue,
                      //  bUDGETTRANSACTIONLINELoc.displayAccountingAmount(),
                      bUDGETTRANSACTIONLINELoc.TransactionCurrencyAmount,
                    bUDGETTRANSACTIONLINELoc.TransactionCurrency);

 

                return false;
                //throw Error(strFmt("@SYS128612",
                //        bUDGETTRANSACTIONHEADERloc.TransactionNumber,
                //        dimensionAttributeValueCombinationlocerror.DisplayValue,
                //      //  bUDGETTRANSACTIONLINELoc.displayAccountingAmount(),
                //      bUDGETTRANSACTIONLINELoc.TransactionCurrencyAmount,
                //        bUDGETTRANSACTIONLINELoc.TransactionCurrency));

            } 
            Amount exbudget;

 

            changecompany(bUDGETTRANSACTIONLINELoc.DAX_TOCOMPANY)
            {
                CurrencyExchangeHelper  currencyExchangeHelper;
                currencyExchangeHelper = CurrencyExchangeHelper::newExchangeDate(Ledger::current(), systemDateGet());
                CurrencyCode tocurrCode = Ledger::accountingCurrency(CompanyInfo::current());
                currencyExchangeHelper.parmLedgerRecId(Ledger::current());
                currencyExchangeHelper.parmExchangeRateTypeRecId(Ledger::budgetExchangeRateType());
                exbudget = currencyExchangeHelper.calculateCurrencyToCurrency(tocurrCode,bUDGETTRANSACTIONLINELoc.TransactionCurrency,budgetAmount,true);
            }

 

            if(exbudget >= currentAmount)
            {
                ret = true;
            }
            else
            {
               /* if(_detailResult)
                {
                    ttsbegin;
                    _detailResult.selectForUpdate(true);
                    _detailResult.AccountingCurrencyAmountOverAvailable = currentAmount - exbudget;
                    _detailResult.update();
                    ttscommit;
                } */
                Amount value = currentAmount - exbudget;

 

                if(!errormsg)
                {
                    errormsg = strFmt("@SYS128612",
                        bUDGETTRANSACTIONHEADERloc.TransactionNumber,
                        dimensionAttributeValueCombinationlocerror.DisplayValue,
                      //  bUDGETTRANSACTIONLINELoc.displayAccountingAmount(),
                      value,
                    bUDGETTRANSACTIONLINELoc.TransactionCurrency);
                }
            }
        }
        return ret;
    }

 

   public void ttsNotifyPreCommit()
    {
        BudgetSource  budgetSource,budgetSourceloc = currentSource;
        BudgetCheckResultErrorWarningDetail _detailResult;
        PurchReqLine purchReqLine;
        SourceDocumentLine  sourceDocumentLine;
        VendInvoiceInfoLine   vendInvoiceInfoLine;

        next ttsNotifyPreCommit();

 

        select  forupdate budgetSource
                where budgetSource.RecId == budgetSourceloc.RecId;
        select purchReqLine
            where purchReqLine.SourceDocumentLine == budgetSourceloc.SourceDocumentLine;
        PurchReqTable purchReqTable =PurchReqTable::find(purchReqLine.PurchReqTable);


        if(budgetSource && budgetSource.CheckResult == BudgetCheckResult::Failed && strStartsWith(purchReqTable.YNV_PurchReqStatus,"DoA"))
        {
            ttsbegin;
            budgetSource.selectForUpdate(true);
            budgetSource.CheckResult = BudgetCheckResult::PassedWithWarnings;
            budgetSource.update();
            ttscommit;
        }

 

select * from vendInvoiceInfoLine
            where vendInvoiceInfoLine.SourceDocumentLine == budgetSourceloc.SourceDocumentLine;

 

       // VendInvoiceInfoTable VendInvoiceInfoTable = VendInvoiceInfoTable::find(vendInvoiceInfoLine.SourceDocumentLine);

 

        if(budgetSource && budgetSource.CheckResult == BudgetCheckResult::Failed && vendInvoiceInfoLine && purchReqTable)
        {
            ttsbegin;
            budgetSource.selectForUpdate(true);
            budgetSource.CheckResult = BudgetCheckResult::PassedWithWarnings;
            budgetSource.update();
            ttscommit;
        }

        result = this.checkBudgetAmount(budgetSourceloc,_detailResult);

        if(result)
        {
            select  forupdate budgetSource
                where budgetSource.RecId == budgetSourceloc.RecId;
            if(budgetSource)
            {
                ttsbegin;
                budgetSource.selectForUpdate(true);
                budgetSource.CheckResult = BudgetCheckResult::Passed;
                budgetSource.update();
                ttscommit;
            }
        }
        else
        {
            if(errormsg)
            {
                select forupdate _detailResult
                    where _detailResult.BudgetSource == budgetSourceloc.RecId;

 

                if(_detailResult)
                {
                    ttsbegin;
                    _detailResult.CheckResult = BudgetCheckResult::Failed;
                    _detailResult.update();
                    ttscommit;
                }

 

                select  forupdate budgetSource
                    where budgetSource.RecId == budgetSourceloc.RecId;
                if(budgetSource)
                {
                    ttsbegin;
                    budgetSource.selectForUpdate(true);
                    budgetSource.CheckResult = BudgetCheckResult::Failed;
                    budgetSource.update();
                    ttscommit;
                }

 

               // throw Error(errormsg);
            }
        }

    }

 

    protected boolean determineMessagesForCheckResultDetails(
        LedgerRecId _primaryLedgerId,
        BudgetCheckResultErrorWarningDetail _detailResult,
        BudgetCheckResultErrorWarningDetail _groupResult)
    {
        BudgetSource                    budgetSource;
        BUDGETTRANSACTIONLINE           bUDGETTRANSACTIONLINE;
        BudgetTransactionHeader         bUDGETTRANSACTIONHEADERloc;

        str toCompany;

 

        select budgetSource
            where budgetSource.RecId == _detailResult.budgetSource;

 

        select bUDGETTRANSACTIONLINE
            where bUDGETTRANSACTIONLINE.RecId == budgetSource.BudgetTransactionLine;

 

        select bUDGETTRANSACTIONHEADERloc
            where bUDGETTRANSACTIONHEADERloc.RecId == bUDGETTRANSACTIONLINE.BudgetTransactionHeader;

 

        boolean ret,check;

 

        if(bUDGETTRANSACTIONHEADERloc.YNV_TransferCrossCompany == NoYes::Yes && /*bUDGETTRANSACTIONLINE.YNV_ToCompany != curExt() && */ bUDGETTRANSACTIONLINE.displayAccountingAmount() < 0)
        {
            result = this.checkBudgetAmount(budgetSource,_detailResult);

            ttsbegin;
            _detailResult.selectForUpdate(true);
            _detailResult.CheckResult = BudgetCheckResult::Passed;
            _detailResult.update();
            ttscommit;
        }

        ret = next determineMessagesForCheckResultDetails(_primaryLedgerId,_detailResult,_groupResult);

        return ret;
    }

 

    public void ttsNotifyCommit()
    {
        next ttsNotifyCommit();

 

        if(errormsg)
        {
            throw Error(errormsg);
        }
    }

 

}

 

Thursday, February 5, 2026

Filtering Grid Data Using a Display Method and Unbound Controls in D365f&o

Step 1: Add an Unbound Control for the Display Method

Create an unbound control on the form and bind it to the display method you want to show. This control is used only for display purposes and does not store data directly.

Step 2: Add a Field to the Table

Add a new field in the table where the logic of the display method will be written. This field acts as a helper to support filtering, since display methods alone cannot be directly used in queries.

Step 3: Update the Field Inside the Display Method

In the display method, update the newly added table field with the calculated value. This ensures the field always reflects the same data shown by the display method.

Step 4: Add an Unbound Control for Filtering

Place another unbound control above the grid. This control will be used as a filter input (for example, a string or enum value entered by the user).

Step 5: Apply Filtering Logic in the Data Source

Write the filtering logic in the executeQuery() method of the data source associated with the display method. Use the value from the unbound filter control to modify the query dynamically.

Finally, call the executeQuery() method from the modified() method of the unbound filter control so that the grid refreshes whenever the filter value changes.

Code in Execute query :

  FormDataSource   wHSInventReserve_ds = this.dataSource(formDataSourceStr(WHSInventOnHandReserve,WHSInventReserve ));

  FormStringControl   NMCOPTFilter = this.design().controlName(formControlStr(WHSInventOnHandReserve, NMCOPTFilter)); //Unbound control   

 SysQuery::findOrCreateRange(wHSInventReserve_ds.query().dataSourceTable(tableNum(WHSInventReserve)),

                                  fieldNum(WHSInventReserve, NMCOutsideProcessTag))

                                  .value(SysQuery::valueLikeAfter(!NMCOPTFilter.text() ? '*' : NMCOPTFilter.text()));








Wednesday, January 28, 2026

Limit the Records in Sales order list page- D365FO

 Navigate to Accounts Receivable Parameter ->General -> Sales Setup

Default value 0 disables the limit. As per the help text given , a recommended value of 1 to 200 can be given for optimal performance.

I tested with a value of 25 and the ListPage shows exactly 25 records. How are these records being selected? It appears to be displaying the orders sorted in descending order by Order Number.

Wednesday, January 21, 2026

Debugging Large Loops in D365 F&O

 When you are new to D365 Finance & Operations (or X++), debugging can feel overwhelming — especially when your code runs inside large loops.

Let me start with a situation almost every beginner faces.


The Common Beginner Problem

You write code like this:

while select employee
{
    employeeId = employee.PersonnelNumber;

    // complex logic
} 

Now imagine:

  • The loop runs for hundreds of employees
  • A bug happens only for one employee (E0001)

You put a breakpoint inside the loop…

And then:

  • The debugger stops again and again
  • You keep pressing F5
  • You lose focus and patience

This is normal when you are starting out.


What I Used to Do Earlier (Many Beginners Still Do)

Earlier in my career, I used this trick:

if (employeeId == 'E0001')
{
    int x = 0;   // breakpoint here
} 

It works — the debugger stops only for that employee


The Right Tool: Conditional Breakpoints

Visual Studio allows you to put a condition on a breakpoint.

Meaning:

“Stop execution only when a condition is true

And the best part? 👉 No code change required


How to Add a Conditional Breakpoint (Step-by-Step)

  1. Put a breakpoint on the required line
  2. Right-click the breakpoint → Conditions
  3. Enable Conditions
  4. Enter the condition:

employeeId == "E0001" 

Now the debugger will:

  • Skip all other employees
  • Stop only when E0001 is processed


Article content
Article content



⚠️ Very Important for Newbies: String Syntax Gotcha

In X++ code, this works:

if (employeeId == 'E0001') 

But in a conditional breakpoint, this does NOT work:

employeeId == 'E0001'   ❌ 

Why?

Because the debugger treats single quotes as a character, not a string.

✅ Always use double quotes in debugger conditions:

employeeId == "E0001" 

Or even safer:

strCmp(employeeId, "E0001") == 0 

This small detail can save you a lot of confusion.


Where Should You Place the Breakpoint?

Always place it after the value is assigned:

employeeId = employee.PersonnelNumber;
// breakpoint here 

If you place it before assignment, the condition will never be true.