B2B pricing engine: customer price, volume tiers, promo, VAT and rounding

Backend Live Coding November 13, 2026 python

Points I explain:

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# ======================================================================
# Q: Implement B2B pricing: customer price > volume tier discount > promo, then
# VAT and rounding.
#
# Points I explain:
# - Decimal everywhere and explicit ROUND_HALF_UP (Python's default is
#   ROUND_HALF_EVEN, which does not agree with the ERP/accounting). Unit price
#   with 4 decimals, amounts with 2.
# - The order of precedence and whether discounts stack is a business decision,
#   not mine. I write it down explicitly and confirm it in writing.
# - Pure function: no database access. The caller loads the PriceContext (one
#   query for all lines).
# - The rule is stored on the order line: when the customer asks "why did I pay
#   this?", there is an answer.
# - VAT per line or on the total gives a one-cent difference: I follow whatever
#   the ERP does, so the invoices agree.
# ======================================================================

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP

CENT = Decimal('0.01')

@dataclass(frozen=True)
class PriceContext:
    list_price: Decimal                    # list price, excluding VAT
    customer_price: Decimal | None         # special price from the customer's price list
    volume_tiers: tuple[tuple[int, Decimal], ...]   # ((50, 0.05), (100, 0.08))
    promo_pct: Decimal | None              # active promo, if any
    vat_rate: Decimal                      # e.g. Decimal('0.24')

@dataclass(frozen=True)
class LinePrice:
    unit_net: Decimal
    net: Decimal
    vat: Decimal
    gross: Decimal
    rule: str                              # which rule was applied (audit)

def price_line(ctx: PriceContext, qty: int) -> LinePrice:
    if qty <= 0:
        raise ValueError('qty must be positive')
    if ctx.customer_price is not None:     # 1. agreed price: does not stack
        unit, rule = ctx.customer_price, 'customer_price'
    else:
        unit, rule = ctx.list_price, 'list'
        tier = max((pct for min_q, pct in ctx.volume_tiers if qty >= min_q),
                   default=Decimal('0'))
        if tier:                           # 2. volume tier discount
            unit, rule = unit * (1 - tier), f'volume_{tier}'
        if ctx.promo_pct and ctx.promo_pct > tier:   # 3. promo only if it is better
            unit, rule = ctx.list_price * (1 - ctx.promo_pct), 'promo'
    unit = unit.quantize(Decimal('0.0001'), ROUND_HALF_UP)
    net = (unit * qty).quantize(CENT, ROUND_HALF_UP)
    vat = (net * ctx.vat_rate).quantize(CENT, ROUND_HALF_UP)   # VAT per line
    return LinePrice(unit, net, vat, net + vat, rule)