refactor discount calculations in create_shop_orders_on_payment to use Decimal for precision

This commit is contained in:
Parsa Nazer
2026-01-06 11:16:31 +03:30
parent 20ef2fe5fe
commit 4badbf4fbf
+34 -32
View File
@@ -8,6 +8,7 @@ from account.models import PushSubscription, UserAddressModel
import ghasedak_sms
from .tasks import send_change_status_notif, send_change_status_sms, send_shop_order_invoice_telegram_task
from django.conf import settings
from decimal import Decimal
@receiver(pre_save, sender=OrderModel)
@@ -82,7 +83,7 @@ def create_shop_orders_on_payment(sender, instance: OrderModel, created, **kwarg
if not shop_groups:
return
# Totals for proportional distribution
# Totals for proportional distribution (for cart-level discount and tax only)
shop_subtotals = {}
for shop, items_list in shop_groups.items():
subtotal = 0
@@ -93,43 +94,45 @@ def create_shop_orders_on_payment(sender, instance: OrderModel, created, **kwarg
total_subtotal = sum(shop_subtotals.values()) or 1
order_discount = int(instance.discount_amount or 0)
order_special_discount = int(instance.special_discount_total or 0)
order_tax = int(instance.tax or 0)
with transaction.atomic():
for shop, items_list in shop_groups.items():
shop_subtotal = shop_subtotals.get(shop, 0)
# Calculate total item-level discounts for this shop
item_level_discounts = 0
# Calculate total item-level discounts for this shop (discount_percent)
item_level_discounts = Decimal('0')
for it in items_list:
item_discount = int(it.price) * int(it.quantity) * (int(it.discount_percent or 0) / 100.0)
item_level_discounts += int(item_discount)
item_discount = Decimal(str(it.price)) * Decimal(str(it.quantity)) * (Decimal(str(it.discount_percent or 0)) / Decimal('100'))
item_level_discounts += item_discount
# proportionally allocate cart-level discount, special discount and tax
allocated_discount = int(
order_discount * shop_subtotal / total_subtotal) if order_discount else 0
allocated_special_discount = int(
order_special_discount * shop_subtotal / total_subtotal) if order_special_discount else 0
allocated_tax = int(order_tax * shop_subtotal /
total_subtotal) if order_tax else 0
# Calculate total item-level special discounts for this shop
# (already calculated as profit * special_discount_percent)
item_special_discounts = Decimal('0')
for it in items_list:
item_special_discounts += Decimal(str(it.special_discount_amount or 0))
# Proportionally allocate cart-level discount and tax
allocated_discount = (Decimal(str(order_discount)) * Decimal(str(shop_subtotal)) / Decimal(str(total_subtotal))) if order_discount else Decimal('0')
allocated_tax = (Decimal(str(order_tax)) * Decimal(str(shop_subtotal)) / Decimal(str(total_subtotal))) if order_tax else Decimal('0')
commission_percent = getattr(shop, 'commission_percent', 0) or 0
try:
commission_percent_value = float(commission_percent)
commission_percent_value = Decimal(str(commission_percent))
except Exception:
commission_percent_value = 0.0
commission_percent_value = Decimal('0')
# commission is calculated on the subtotal after ALL discounts (item-level, cart-level, and special)
# but BEFORE tax
# Commission is calculated on the subtotal after ALL discounts:
# subtotal - item_discount - item_special_discount - cart_discount
base_for_commission = max(
0, shop_subtotal - item_level_discounts - allocated_discount - allocated_special_discount)
commission_amount = int(
base_for_commission * (commission_percent_value / 100.0))
Decimal('0'),
Decimal(str(shop_subtotal)) - item_level_discounts - item_special_discounts - allocated_discount
)
commission_amount = base_for_commission * (commission_percent_value / Decimal('100'))
# Payable to shop: subtotal minus all discounts minus commission (no tax added to payable)
payable = shop_subtotal - item_level_discounts - allocated_discount - \
allocated_special_discount - commission_amount
payable = Decimal(str(shop_subtotal)) - item_level_discounts - item_special_discounts - \
allocated_discount - commission_amount
# Prepare customer information
customer_phone = (instance.user.phone or '') if instance.user else ''
@@ -182,28 +185,27 @@ def create_shop_orders_on_payment(sender, instance: OrderModel, created, **kwarg
is_paid=instance.is_paid,
subtotal=shop_subtotal,
items_count=sum(int(it.quantity) for it in items_list),
discount_amount=allocated_discount,
discount_amount=int(allocated_discount),
special_discount_code=instance.special_discount_code,
special_discount_amount=allocated_special_discount,
commission_percent=commission_percent_value,
commission_amount=commission_amount,
tax_amount=allocated_tax,
payable_amount=payable,
special_discount_amount=int(item_special_discounts),
commission_percent=float(commission_percent_value),
commission_amount=int(commission_amount),
tax_amount=int(allocated_tax),
payable_amount=int(payable),
order_created_at=order_created_datetime,
)
# Create ShopOrderItem rows linking to original OrderItemModel
for it in items_list:
item_discount_amount = Decimal(str(it.price)) * Decimal(str(it.quantity)) * (Decimal(str(it.discount_percent or 0)) / Decimal('100'))
ShopOrderItem.objects.create(
shop_order=shop_order,
order_item=it,
quantity=int(it.quantity),
unit_price=int(it.price),
total_price=int(it.price) * int(it.quantity),
discount_amount=int(
it.price) * int(it.quantity) * (int(it.discount_percent or 0) / 100.0),
special_discount_amount=int(
it.special_discount_amount or 0),
discount_amount=int(item_discount_amount),
special_discount_amount=int(it.special_discount_amount or 0),
)