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 import ghasedak_sms
from .tasks import send_change_status_notif, send_change_status_sms, send_shop_order_invoice_telegram_task from .tasks import send_change_status_notif, send_change_status_sms, send_shop_order_invoice_telegram_task
from django.conf import settings from django.conf import settings
from decimal import Decimal
@receiver(pre_save, sender=OrderModel) @receiver(pre_save, sender=OrderModel)
@@ -82,7 +83,7 @@ def create_shop_orders_on_payment(sender, instance: OrderModel, created, **kwarg
if not shop_groups: if not shop_groups:
return return
# Totals for proportional distribution # Totals for proportional distribution (for cart-level discount and tax only)
shop_subtotals = {} shop_subtotals = {}
for shop, items_list in shop_groups.items(): for shop, items_list in shop_groups.items():
subtotal = 0 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 total_subtotal = sum(shop_subtotals.values()) or 1
order_discount = int(instance.discount_amount or 0) 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) order_tax = int(instance.tax or 0)
with transaction.atomic(): with transaction.atomic():
for shop, items_list in shop_groups.items(): for shop, items_list in shop_groups.items():
shop_subtotal = shop_subtotals.get(shop, 0) shop_subtotal = shop_subtotals.get(shop, 0)
# Calculate total item-level discounts for this shop # Calculate total item-level discounts for this shop (discount_percent)
item_level_discounts = 0 item_level_discounts = Decimal('0')
for it in items_list: for it in items_list:
item_discount = int(it.price) * int(it.quantity) * (int(it.discount_percent or 0) / 100.0) item_discount = Decimal(str(it.price)) * Decimal(str(it.quantity)) * (Decimal(str(it.discount_percent or 0)) / Decimal('100'))
item_level_discounts += int(item_discount) item_level_discounts += item_discount
# proportionally allocate cart-level discount, special discount and tax # Calculate total item-level special discounts for this shop
allocated_discount = int( # (already calculated as profit * special_discount_percent)
order_discount * shop_subtotal / total_subtotal) if order_discount else 0 item_special_discounts = Decimal('0')
allocated_special_discount = int( for it in items_list:
order_special_discount * shop_subtotal / total_subtotal) if order_special_discount else 0 item_special_discounts += Decimal(str(it.special_discount_amount or 0))
allocated_tax = int(order_tax * shop_subtotal /
total_subtotal) if order_tax else 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 commission_percent = getattr(shop, 'commission_percent', 0) or 0
try: try:
commission_percent_value = float(commission_percent) commission_percent_value = Decimal(str(commission_percent))
except Exception: 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) # Commission is calculated on the subtotal after ALL discounts:
# but BEFORE tax # subtotal - item_discount - item_special_discount - cart_discount
base_for_commission = max( base_for_commission = max(
0, shop_subtotal - item_level_discounts - allocated_discount - allocated_special_discount) Decimal('0'),
commission_amount = int( Decimal(str(shop_subtotal)) - item_level_discounts - item_special_discounts - allocated_discount
base_for_commission * (commission_percent_value / 100.0)) )
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 to shop: subtotal minus all discounts minus commission (no tax added to payable)
payable = shop_subtotal - item_level_discounts - allocated_discount - \ payable = Decimal(str(shop_subtotal)) - item_level_discounts - item_special_discounts - \
allocated_special_discount - commission_amount allocated_discount - commission_amount
# Prepare customer information # Prepare customer information
customer_phone = (instance.user.phone or '') if instance.user else '' 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, is_paid=instance.is_paid,
subtotal=shop_subtotal, subtotal=shop_subtotal,
items_count=sum(int(it.quantity) for it in items_list), 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_code=instance.special_discount_code,
special_discount_amount=allocated_special_discount, special_discount_amount=int(item_special_discounts),
commission_percent=commission_percent_value, commission_percent=float(commission_percent_value),
commission_amount=commission_amount, commission_amount=int(commission_amount),
tax_amount=allocated_tax, tax_amount=int(allocated_tax),
payable_amount=payable, payable_amount=int(payable),
order_created_at=order_created_datetime, order_created_at=order_created_datetime,
) )
# Create ShopOrderItem rows linking to original OrderItemModel # Create ShopOrderItem rows linking to original OrderItemModel
for it in items_list: 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( ShopOrderItem.objects.create(
shop_order=shop_order, shop_order=shop_order,
order_item=it, order_item=it,
quantity=int(it.quantity), quantity=int(it.quantity),
unit_price=int(it.price), unit_price=int(it.price),
total_price=int(it.price) * int(it.quantity), total_price=int(it.price) * int(it.quantity),
discount_amount=int( discount_amount=int(item_discount_amount),
it.price) * int(it.quantity) * (int(it.discount_percent or 0) / 100.0), special_discount_amount=int(it.special_discount_amount or 0),
special_discount_amount=int(
it.special_discount_amount or 0),
) )