refactor discount calculations in create_shop_orders_on_payment to use Decimal for precision
This commit is contained in:
+34
-32
@@ -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),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user