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
- Never hardcode a price or a discount. Everything is a dated condition record.
- Build the pricing trace view early. “Why is this price 47.50?” is a weekly question forever.
- Store the resolved conditions on the document, not just the total. Reprinting an invoice must show the same breakdown years later.
- Statistical conditions (cost, margin) are essential for reporting and must not affect the total.
- Effective dating on everything, and be explicit about which date drives the lookup.
- 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.
- Rebate accruals are a liability and must reconcile to the settlement.