"""Backend-authoritative pricing engine. Produces a full, explainable breakdown and is always the source of truth — the frontend may display estimates but must never be trusted for financial values. """ from decimal import Decimal, ROUND_HALF_UP CHARGE_TYPES = {"fixed", "per_kg", "per_unit", "percentage", "tiered", "fee"} class PricingEngine: def calculate(self, weight_kg, selected_steps, currency: str = "IRR") -> dict: weight = Decimal(str(weight_kg)) lines = [] subtotal = Decimal("0") # Pass 1 — charge rules. for step in selected_steps: for rule in step.pricing_rules.filter(active=True, rule_type__in=CHARGE_TYPES).order_by("priority"): line = self._apply_charge_rule(rule, weight) if line is None: continue lines.append(line) subtotal += Decimal(line["amount"]) # Pass 2 — discounts (fixed or percentage of subtotal). discounts = Decimal("0") for step in selected_steps: for rule in step.pricing_rules.filter(active=True, rule_type="discount"): amt = Decimal(str(rule.amount)) if (rule.config or {}).get("percentage"): amt = subtotal * amt / Decimal("100") discounts += amt lines.append( self._line(rule, weight, "discount", Decimal("0"), amt) ) # Pass 3 — taxes on (subtotal - discounts). taxes = Decimal("0") taxable = subtotal - discounts for step in selected_steps: for rule in step.pricing_rules.filter(active=True, rule_type="tax"): amt = taxable * Decimal(str(rule.amount)) / Decimal("100") taxes += amt lines.append(self._line(rule, weight, "tax", Decimal("0"), amt)) total = subtotal - discounts + taxes if total < 0: total = Decimal("0") return { "currency": currency, "lines": lines, "subtotal": str(self._q(subtotal)), "discounts": str(self._q(discounts)), "taxes": str(self._q(taxes)), "total": str(self._q(total)), } def _q(self, v: Decimal) -> Decimal: return v.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) def _line(self, rule, weight, kind, unit_price, amount, quantity=None) -> dict: return { "step_key": rule.step.key, "rule": rule.name, "kind": kind, "quantity": str(quantity if quantity is not None else weight), "unit_price": str(self._q(unit_price)), "amount": str(self._q(amount)), } def _apply_charge_rule(self, rule, weight): kind = rule.rule_type cfg = rule.config or {} amount = Decimal(str(rule.amount)) unit_price = Decimal("0") quantity = weight if kind == "fixed": unit_price = amount elif kind == "per_kg": unit_price = amount amount = amount * weight elif kind == "per_unit": units = Decimal(str(cfg.get("units", 1))) unit_price = amount amount = amount * units quantity = units elif kind == "percentage": base = Decimal(str(cfg.get("base", 0))) unit_price = amount amount = base * amount / Decimal("100") elif kind == "tiered": unit_price = self._tier_unit_price(cfg.get("tiers", []), weight) amount = unit_price * weight elif kind == "fee": unit_price = Decimal("0") else: return None min_c = cfg.get("min_charge") max_c = cfg.get("max_charge") if min_c is not None and amount < Decimal(str(min_c)): amount = Decimal(str(min_c)) if max_c is not None and amount > Decimal(str(max_c)): amount = Decimal(str(max_c)) return self._line(rule, weight, kind, unit_price, amount, quantity) def _tier_unit_price(self, tiers, weight) -> Decimal: best = Decimal("0") for tier in tiers or []: low = Decimal(str(tier.get("from", 0))) if weight >= low: best = Decimal(str(tier.get("price", 0))) return best