Dynamics F&O Insights
Saturday, May 16, 2026
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 :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
[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)
- Put a breakpoint on the required line
- Right-click the breakpoint → Conditions
- Enable Conditions
- Enter the condition:
employeeId == "E0001" Now the debugger will:
- Skip all other employees
- Stop only when E0001 is processed
⚠️ 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.