The Pricing Engine

Pricing is the most under-estimated part of any ERP. “Price × quantity” is never the requirement. A real pricing engine is a small rules engine, and every ERP has one.

The condition technique

The universal pattern. Four layers:

1. CONDITION TYPE      what kind of pricing element  (base price, discount, freight, tax)
2. ACCESS SEQUENCE     the ordered list of keys to search
3. CONDITION TABLE     where the actual rates live
4. PRICING PROCEDURE   which condition types apply, in what order, with what arithmetic

Condition types

Type Purpose Sign
PR00 Base price +
K004 Material discount −
K005 Customer/material discount −
K007 Customer discount % −
K020 Price group discount −
KF00 Freight +
HD00 Header freight +
SKTO Cash discount (statistical) −
MWST Output tax +
VPRS Cost (statistical, for margin) —
KUMU Cumulative rebate accrual −

Each condition type has properties: calculation type (fixed amount, percentage, per unit, per weight/volume), scale basis, whether it’s manual or automatic, whether it’s statistical (informational, not added to the total), and its GL account key.

Access sequence

The lookup order — most specific wins:

Condition type K004 (material discount), access sequence:
  10  Customer + Material + Sales org       ← most specific
  20  Customer group + Material
  30  Price list + Material
  40  Material                              ← most general

The engine stops at the first hit.

This is why “the price is wrong” investigations always start with “which access found the record?” Every serious ERP has a pricing analysis screen showing exactly which accesses were tried, which hit, and why the others missed. If you build a pricing engine, build this trace view first — you will need it every single week.

Condition records

condition_record(
  id, condition_type,
  -- key fields, whichever the access uses
  sales_org, distribution_channel, customer_id, customer_group,
  material_id, material_group, price_list,
  valid_from DATE, valid_to DATE,
  calculation_type,     -- FIXED | PERCENT | PER_UNIT | PER_WEIGHT | PER_VOLUME
  rate NUMERIC(19,6), currency, per_quantity, uom,
  scale_type,           -- NONE | FROM | TO
  deletion_flag
)
condition_scale(condition_record_id, scale_quantity, scale_rate)

Everything is effective-dated. The pricing date on the document (which may be the order date, delivery date, or billing date — a config choice) selects which record applies. This is how a price increase is loaded in advance and takes effect automatically.

Scales (quantity breaks)

Order 1–99 units      → 50.00 each
Order 100–499 units   → 47.50 each
Order 500–999 units   → 45.00 each
Order 1000+ units     → 42.00 each

Scale bases: quantity, value, weight, volume, or points. Graduated vs non-graduated matters: graduated applies each tier to its own band (like income tax); non-graduated applies the reached tier’s rate to everything. Ask which one — the answer is usually non-graduated in sales and graduated in rebates, and getting it backwards changes invoices materially.

The pricing procedure

The calculation sheet:

Step  Cnt  CType  Description           From  To   Man  Req  Stat  AcctKey
 10   1    PR00   Base price                            X              ERL
 20   1           Gross value            10   10
 30   1    K004   Material discount      20                            ERS
 31   1    K005   Cust/Mat discount      20                            ERS
 40   1           Net after discount     20   39
 50   1    KF00   Freight                40                            ERF
 60   1    VPRS   Cost                                        X        —
 70   1           Margin                 40   40
 80   1    MWST   Output tax             40                            MWS
 90   1           Final amount           40   80

Columns:

  • From/To — the step range this condition calculates on (a percentage of what?)
  • Man — manual entry allowed
  • Req — mandatory; the document is incomplete without it
  • Stat — statistical: shown but not added to the total (cost, margin, cash discount)
  • AcctKey — which GL account this element posts to

Subtotals are the rows without a condition type. They’re the reference points everything else calculates from. Getting the from/to references right is the whole game — a discount referencing the wrong subtotal compounds against another discount and produces wrong invoices at scale.

Pricing scenarios you will meet

List price with customer-specific overrides

Base price list, then a negotiated price for specific customers on specific materials, effective-dated.

Cost-plus

Standard cost 32.00 × (1 + markup 35%) = 43.20

The cost condition must be resolved at pricing time from the material’s current valuation.

Contract pricing

Price fixed by contract, with the contract number referenced on the order. Must not be overridden by list price changes.

Price lists by region/channel/currency

Distinct price lists for domestic/export, retail/wholesale, per currency.

Promotions

Time-boxed, with a promotion ID for reporting, often stacked with rules about which promotions can combine.

Rebates

Retrospective volume discounts. The hard one.

Agreement: 3% rebate if annual purchases exceed 10,000,000

During the year:  accrue 3% on each invoice
                    Rebate expense    Dr
                      Rebate accrual     Cr    (a liability)
At settlement:    calculate actual, issue a credit memo, clear the accrual

Complications: tiered rebates (2% over 5M, 3% over 10M), rebates at customer-hierarchy level (a buying group), rebates on product groups, and the accrual estimate needing revision as the year progresses. Rebate accruals are a material balance-sheet item in retail supply and get audited closely.

Chargebacks (distribution)

The distributor sells to an end customer at a contract price you negotiated, then bills you the difference from their acquisition cost. Standard in pharma and electronics distribution, and a whole sub-module in its own right.

Free goods

“Buy 10, get 1 free.”

  • Inclusive — 1 of the 10 is free
  • Exclusive — an extra item is added free

The free item still consumes inventory and COGS, so margin per unit falls even though nothing was discounted on paper.

Multi-currency pricing

Condition records in one currency, order in another. Converted at the pricing date’s rate, using a specified rate type. Store both the source rate and the converted amount.

Where pricing runs

Pricing executes at several points, and which conditions redetermine at each point is configuration:

Point Typical behaviour
Order creation Full pricing
Order change (qty, material, date) Redetermine scales, keep manual entries
Delivery Usually no repricing
Billing Copy from order; optionally redetermine tax and freight
Credit memo Copy from the original invoice

Getting this wrong is a top-three source of customer disputes. Repricing at billing when a price list changed between order and shipment means the customer is billed a price they never agreed to. Document the repricing rule per condition type explicitly.

Performance

Pricing runs on every line of every order. For a bulk EDI order with 500 lines and a procedure with 25 condition types and 5 accesses each, that’s up to 62,500 lookups.

Techniques:

  • Cache condition records per (condition type, key) for the duration of the document
  • Pre-filter by validity date in the query, not in application code
  • Index condition tables on the full access key including validity
  • Fetch all lines’ condition records in one query, not per line (the classic N+1)
  • Precompute for high-volume repeat scenarios where the inputs rarely change

CPQ vs ERP pricing

CPQ (Configure-Price-Quote) tools — Salesforce CPQ, Oracle CPQ, PROS, Tacton — sit in front of the ERP for complex configurable products, guided selling, approval workflows on discounts, and proposal generation.

The boundary: CPQ produces a quote; the ERP is the system of record for the order and must be able to reproduce the pricing. The trap: if CPQ prices and the ERP reprices differently, invoices don’t match quotes. Either the ERP accepts CPQ prices as manual conditions, or both share a pricing service. Pick one and make it explicit.

Developer rules

  1. Never hardcode a price or a discount. Everything is a dated condition record.
  2. Build the pricing trace view early. “Why is this price 47.50?” is a weekly question forever.
  3. Store the resolved conditions on the document, not just the total. Reprinting an invoice must show the same breakdown years later.
  4. Statistical conditions (cost, margin) are essential for reporting and must not affect the total.
  5. Effective dating on everything, and be explicit about which date drives the lookup.
  6. Test the boundaries: scale edges (exactly 100 units), validity edges (the day a price changes), zero and negative quantities, and currency conversion at rate-change boundaries.
  7. Rebate accruals are a liability and must reconcile to the settlement.

Next