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.