SaaS · Customization

Acumatica Pricing and Discount Engine

How to use the Acumatica pricing and discount engine — price lists, volume discounts, customer-class-specific pricing, promotional codes, and the priority rules that resolve conflicts.

John Kihiu12 min read

Acumatica resolves a line price and a line discount as two separate lookups, in a defined order, using the customer, the inventory item, the quantity, and the effective date. Most "why did it charge that?" tickets come down to not knowing that order. Once you can trace which sales price record and which discount sequence won, the engine stops feeling like a black box.

Price and discount are two engines

The unit price on a sales order line comes from the Sales Prices table (screen AR202000), matched by price type, customer or customer price class, item, unit of measure, quantity break, and effective date. The discount is a separate resolution against Discount Codes and their sequences. A line can pick up a base price from one place and then have a line, group, or document discount stacked on top. Keeping these mentally separate is the first step: the price answers "what does this cost," the discount answers "what do we take off."

How the price is resolved

When a line is entered, the engine looks for the most specific applicable sales price and works outward. Customer-specific prices beat customer-price-class prices, which beat the item's base price. Within each level, quantity breaks and the effective/expiration dates filter further, and the promotional (override) prices take precedence over regular ones. The result is the unit price before any discount.

TEXT · PRICE PRECEDENCE
1. Customer + item, promotional price (effective today)
2. Customer + item, regular price (best quantity break)
3. Customer price class + item
4. Base price (item's default price list)
   ↓ first match with a valid effective date wins
Effective dates are inclusive of the document date

The date the engine matches against is the order date, not today. Back-dating an order can pull an expired promotional price, and future-dating can miss a promotion that has not started. When a price looks wrong, check the document date before you touch the price list.

Line, group, and document discounts

Acumatica applies discounts at three levels, and they can compound. A line discount reduces one line. A group discount applies across a set of lines that together meet a threshold (buy 100 across these items, get 5% on all of them). A document discount applies to the order total. Whether they stack or one excludes the others is controlled per discount code and by the discount sequence priority — this is where "we gave them the volume discount and the promo" bugs live.

TEXT · DISCOUNT LEVELS
Line discount    → one SOLine
Group discount   → a qualifying set of lines
Document discount→ the whole SOOrder total

Each discount code has a Sequence; sequences are tried by
priority. A code marked "skip remaining" stops the chain.

Quantity breaks and volume tiers

Both sales prices and discounts support break-by-quantity or break-by-amount tiers. A sales price record can hold several rows — 1–9 at one price, 10–49 at another — and the engine picks the tier the ordered quantity falls into. Volume discounts work the same way on the discount side. The gotcha is unit of measure: a quantity break defined in EA does not automatically apply to a line entered in CS (case) unless the UoM conversion and the break's UoM line up. Mismatched UoM is a common reason a tier "does not trigger."

Recalculation is not always automatic

Changing the customer, adding lines that cross a group threshold, or editing the document date does not always re-run discount resolution on lines already entered. The Recalculate Prices action on the order (with the option to overwrite manual prices, or not) is what forces a clean re-resolution. If a rep changed a price by hand, recalculation will respect or clobber it depending on that flag.

Reading the resolved values in code

When you need to react to the engine — say, block a manual price below the resolved one — the values you care about sit on SOLine: CuryUnitPrice (the resolved unit price), DiscPct and CuryDiscAmt (the line discount), and ManualPrice/ManualDisc flags telling you whether a human overrode the engine. Hook RowUpdated or a field event rather than trying to re-implement resolution yourself.

C# · GRAPH EXTENSION
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
    protected void _(Events.RowUpdated<SOLine> e)
    {
        var row = e.Row;
        if (row == null || row.ManualPrice != true) return;

        // Compare the rep's manual price against what the engine resolved.
        decimal? floor = PXPriceCostAttribute.GetCustomerPrice(
            Base, e.Cache, row, row.OrderDate);

        if (floor.HasValue && row.CuryUnitPrice < floor)
            e.Cache.RaiseExceptionHandling<SOLine.curyUnitPrice>(
                row, row.CuryUnitPrice,
                new PXSetPropertyException(
                    "Price is below the resolved customer price.",
                    PXErrorLevel.Warning));
    }
}

Prefer the framework's own resolution helpers over querying the price tables directly — they honour effective dates, UoM conversion, and precedence exactly the way the UI does, so your validation matches what the rep sees.

Which record wins: a quick map

SituationWhat wins
Customer price and price-class price both matchCustomer-specific
Promotional and regular price both effectivePromotional
Two quantity breaks overlapThe tier the quantity falls into
Manual price entered, then Recalculate runDepends on the "override manual" flag
Multiple discount codes qualifyHighest-priority sequence; "skip remaining" stops the chain

Wrapping up

The pricing and discount engine is deterministic once you accept it as two ordered lookups: most-specific sales price first, then a discount chain resolved by sequence priority. When a number looks wrong, check the document date, the UoM, and whether a manual override or a stale line is in play before you blame the price list. Use the framework's resolution helpers in code so your rules agree with the screen, and reach for Recalculate Prices to get a clean answer.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.