Thursday, 28 May 2026

How to Get Trade Agreement Line Discount Percentage by Item and Date in D365FO Using X++

 


In Microsoft Dynamics 365 Finance and Operations (D365FO), trade agreements are used to manage prices and discounts for customers, vendors, and items. Frequently, developers need to retrieve the applicable line discount percentage for a specific item, customer, and date without creating a sales order.

The recommended approach is to use the PriceDisc framework, which is the standard pricing and discount engine used throughout D365FO. Microsoft identifies the PriceDisc class as the search engine for prices and discounts, while PriceDiscParameters acts as the container for all pricing criteria.

This article explains a practical X++ implementation that retrieves the line discount percentage from trade agreements.

Complete Method

server static DiscPct getLineDiscountPct( ItemId _itemId, CustAccount _custAccount, TransDate _priceDate = systemDateGet())

{

    InventTable             inventTable;

    InventDim               inventDim;

    PriceDisc               priceDisc;

    PriceDiscParameters     priceDiscParameters = PriceDiscParameters::construct();

    DiscPct                 lineDiscPct;

    boolean                 discExist;

    inventTable = InventTable::find(_itemId);

    if (!inventTable)

        return 0;

    // Fix blank date

    if (_priceDate == dateNull())

    { _priceDate = today();

    }

    // Build parameters

    priceDiscParameters.parmModuleType(ModuleInventPurchSales::Sales);

    priceDiscParameters.parmItemId(inventTable.ItemId);

    priceDiscParameters.parmInventDim(InventDim::findOrCreate(inventDim));

    priceDiscParameters.parmUnitID(inventTable.inventTableModuleSales().UnitId);

    priceDiscParameters.parmPriceDiscDate(_priceDate);

    priceDiscParameters.parmQty(1);

    priceDiscParameters.parmAccountNum(_custAccount);

    priceDiscParameters.parmCurrencyCode(

        Ledger::reportingCurrency(CompanyInfo::current()));

    priceDisc = PriceDisc::newFromPriceDiscParameters(priceDiscParameters);

    discExist = priceDisc.findLineDisc('', '');

    if (discExist)

    {        lineDiscPct = priceDisc.lineDiscPct();

    }

    else    {

        lineDiscPct = 0;

    }

    return lineDiscPct;

}


Understanding the Logic

The method determines the line discount percentage that would be applied to an item for a customer on a specific date based on existing trade agreements.

Input Parameters

ItemId      _itemId
CustAccount _custAccount
TransDate _priceDate
ParameterPurpose
_itemIdItem to evaluate
_custAccountCustomer account
_priceDateDate used for trade agreement validity

Example:

DiscPct discount =
MyPricingHelper::getLineDiscountPct(
"A0001",
"US-001",
today());

The method returns the matching trade agreement discount percentage.


Step 1: Retrieve Item Information

inventTable = InventTable::find(_itemId);

The first step retrieves the item record from InventTable.

if (!inventTable)
return 0;

If the item does not exist, there is no reason to continue searching for discounts.


Step 2: Handle Empty Date Values

if (_priceDate == dateNull())
{
_priceDate = today();
}

Trade agreements are date-sensitive.

Every trade agreement contains:

  • From Date
  • To Date

If the caller passes a blank date, the method defaults to today's date.

This ensures the pricing engine evaluates currently active agreements.


Step 3: Create PriceDiscParameters

Microsoft recommends using PriceDiscParameters to pass all pricing and discount criteria into the pricing engine. The PriceDisc class then performs the actual trade agreement search.

PriceDiscParameters priceDiscParameters =
PriceDiscParameters::construct();

This object acts as the search request.


Step 4: Specify Sales Module

priceDiscParameters.parmModuleType(
ModuleInventPurchSales::Sales);

This tells the framework:

Search Sales trade agreements.

Possible values include:

ModuleInventPurchSales::Sales
ModuleInventPurchSales::Purch

Because we are evaluating customer discounts, Sales is appropriate.


Step 5: Set Item

priceDiscParameters.parmItemId(
inventTable.ItemId);

The pricing engine needs to know which item is being evaluated.

The engine will check trade agreement records that match:

  • Specific item
  • Item group
  • All items

depending on the setup.


Step 6: Set Inventory Dimensions

priceDiscParameters.parmInventDim(
InventDim::findOrCreate(inventDim));

This supplies inventory dimensions.

Examples include:

  • Site
  • Warehouse
  • Color
  • Size
  • Style

In your code:

InventDim inventDim;

is blank.

Therefore:

InventDim::findOrCreate(inventDim)

creates an "empty" dimension combination.

This means discounts are searched without dimension-specific restrictions.

If your trade agreements depend on site or warehouse, you should populate these values.

Example:

inventDim.InventSiteId = "1";
inventDim.InventLocationId = "11";

Step 7: Set Sales Unit

priceDiscParameters.parmUnitID(
inventTable.inventTableModuleSales().UnitId);

Trade agreements can be defined per unit.

Example:

UnitDiscount
EA10%
BOX15%

The pricing engine therefore requires the sales unit.


Step 8: Set Pricing Date

priceDiscParameters.parmPriceDiscDate(
_priceDate);

This is one of the most important parameters.

The engine uses it to validate:

From Date <= Price Date <= To Date

Only active agreements are considered.


Step 9: Set Quantity

priceDiscParameters.parmQty(1);

Trade agreements often contain quantity breaks.

Example:

QtyDiscount
1+5%
10+10%
100+20%

By supplying:

parmQty(1)

you are asking:

What discount applies when purchasing one unit?

If quantity-based discounts are important, pass the actual quantity.


Step 10: Set Customer Account

priceDiscParameters.parmAccountNum(
_custAccount);

The pricing engine evaluates customer-specific agreements.

Possible trade agreement relations include:

  • Specific Customer
  • Customer Group
  • All Customers

This parameter enables the framework to resolve the appropriate discount hierarchy.


Step 11: Set Currency

priceDiscParameters.parmCurrencyCode(
Ledger::reportingCurrency(
CompanyInfo::current()));

Currency can affect price and discount searches.

The framework expects a currency code during evaluation.


Step 12: Create the PriceDisc Engine

priceDisc =
PriceDisc::newFromPriceDiscParameters(
priceDiscParameters);

At this point all search criteria have been prepared.

The PriceDisc class now becomes the engine responsible for finding applicable prices and discounts. Microsoft describes PriceDisc as the core search engine used for price and discount determination.


Step 13: Search for Line Discount

discExist = priceDisc.findLineDisc('', '');

This is the most important statement in the method.

The framework searches trade agreements for a matching line discount.

Internally it evaluates:

  • Customer
  • Customer group
  • Item
  • Item group
  • Date validity
  • Quantity
  • Unit
  • Currency

and returns:

true

if a matching discount is found.


Step 14: Retrieve Discount Percentage

if (discExist)
{
lineDiscPct = priceDisc.lineDiscPct();
}

After a successful search:

priceDisc.lineDiscPct()

returns the actual discount percentage.

Example:

Trade Agreement = 15%

Result:

15.00

Step 15: Handle No Match

else
{
lineDiscPct = 0;
}

If no agreement exists:

0

is returned.

This prevents null or unexpected values.


Example Scenario

Assume the following trade agreement exists:

CustomerItemFrom DateTo DateDiscount
US-001A000101-Jan-202531-Dec-202612%

Call:

DiscPct pct =
getLineDiscountPct(
"A0001",
"US-001",
today());

Result:

12.00

Potential Improvements

1. Pass Actual Quantity

Instead of:

parmQty(1);

consider:

parmQty(_qty);

This ensures quantity-based trade agreements are correctly evaluated.


2. Pass Actual Currency

Instead of reporting currency:

Ledger::reportingCurrency(...)

consider using the customer's sales currency:

CustTable::find(_custAccount).Currency

This better reflects real sales order pricing behavior.


3. Pass Actual Inventory Dimensions

If discounts depend on:

  • Site
  • Warehouse
  • Configuration
  • Size
  • Color

populate the InventDim record before creating the pricing request.


Why Use PriceDisc Instead of Querying Trade Agreement Tables Directly?

Many developers try to read PriceDiscTable or PriceDiscAdmTrans directly.

This is not recommended because the framework already handles:

  • Customer hierarchy
  • Customer groups
  • Item groups
  • Quantity breaks
  • Date validity
  • Unit conversions
  • Currency handling
  • Search priorities

Using PriceDisc ensures your logic matches standard D365FO sales order pricing behavior. Microsoft specifically positions PriceDisc as the framework responsible for price and discount determination throughout the application.

Conclusion

The method shown above is a clean and framework-compliant way to retrieve a trade agreement line discount percentage in D365FO. By constructing a PriceDiscParameters object and passing it into the PriceDisc engine, you leverage the same pricing logic used by sales orders and other standard processes.

The key flow is:

  1. Validate item and date.
  2. Build PriceDiscParameters.
  3. Populate customer, item, quantity, unit, currency, and date.
  4. Instantiate PriceDisc.
  5. Call findLineDisc().
  6. Retrieve the result using lineDiscPct().

This approach guarantees that your custom code remains aligned with the standard D365FO pricing framework and future platform enhancements.

No comments:

How to Get Trade Agreement Line Discount Percentage by Item and Date in D365FO Using X++

  In Microsoft Dynamics 365 Finance and Operations (D365FO), trade agreements are used to manage prices and discounts for customers, vendor...