Files
reseller-platform/backend/app/engines/pricing.py

88 lines
2.7 KiB
Python

"""Pricing engine.
Applies active `price_rules` (cost -> markup bands) and computes net profit:
retail = cost * (1 + markup_pct/100) (floored at cost + shipping + fees)
net profit = retail - cost - shipping - fees
margin_pct = profit / retail * 100
`min_price` (product floor) is respected — retail is never below it.
"""
from decimal import ROUND_HALF_UP, Decimal
from app.models import PriceRule
CENT = Decimal("0.01")
def _q(value) -> Decimal:
try:
return Decimal(str(value or 0))
except Exception:
return Decimal("0")
def get_active_rules(db):
return (
db.query(PriceRule)
.filter(PriceRule.active.is_(True))
.order_by(PriceRule.min_cost)
.all()
)
def find_rule(db, cost):
cost = _q(cost)
for rule in get_active_rules(db):
if cost >= _q(rule.min_cost) and (rule.max_cost is None or cost < _q(rule.max_cost)):
return rule
return None
def calculate_price(db, cost, shipping=0, fees=0, min_price=None):
"""Return dict(retail, profit, margin, margin_pct, markup_pct, rule_id, ...) or None.
None means no active rule applies — callers must NOT publish such products.
"""
cost = _q(cost)
shipping = _q(shipping)
fees = _q(fees)
rule = find_rule(db, cost)
if rule is None:
return None
markup_pct = _q(rule.markup_pct)
retail = (cost * (1 + markup_pct / 100)).quantize(CENT, rounding=ROUND_HALF_UP)
# floor: cost + shipping + fees, raised by min_margin_pct if configured
floor = (cost + shipping + fees).quantize(CENT, rounding=ROUND_HALF_UP)
min_margin_pct = _q(rule.min_margin_pct)
if min_margin_pct > 0:
floor = max(floor, (floor + retail * min_margin_pct / 100).quantize(CENT, rounding=ROUND_HALF_UP))
if retail < floor:
retail = floor
# never below the product's minimum price
if min_price is not None and retail < _q(min_price):
retail = _q(min_price).quantize(CENT, rounding=ROUND_HALF_UP)
profit = (retail - cost - shipping - fees).quantize(CENT, rounding=ROUND_HALF_UP)
margin_pct = (profit / retail * 100).quantize(Decimal("0.01")) if retail > 0 else Decimal("0")
return {
"cost": cost,
"shipping": shipping,
"fees": fees,
"retail": retail,
"profit": profit,
"margin": profit,
"margin_pct": float(margin_pct),
"markup_pct": float(markup_pct),
"rule_id": rule.id,
}
def estimate_net_profit(retail, cost, shipping=0, fees=0):
"""Net profit = retail - (cost + shipping + fees)."""
return (_q(retail) - _q(cost) - _q(shipping) - _q(fees)).quantize(CENT, rounding=ROUND_HALF_UP)