Files
hossein-por-shop/backend/order/signals.py
T

231 lines
8.8 KiB
Python

from .models import ShopOrderModel, ShopOrderItem
from django.db import transaction
from django.db.models.signals import post_save
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import OrderModel
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
@receiver(pre_save, sender=OrderModel)
def order_status_changed(sender, instance, **kwargs):
if instance.pk:
previous = OrderModel.objects.get(pk=instance.pk)
if previous.status != instance.status:
new_status = instance.get_status_display()
# send_change_status_notif.delay(instance.pk, new_status)
# send_change_status_sms.delay(instance.pk, new_status)
if previous.status == 'CART' and instance.status == 'ADMIN_PENDING':
# update_cart_price_fields()
# update_sell_data()
# update_quantity()
pass
@receiver(pre_save, sender=OrderModel)
def set_default_address(sender, instance, **kwargs):
if instance.address is None and instance.user:
default_address = UserAddressModel.objects.filter(
user=instance.user, is_main=True).first()
if default_address:
instance.address = default_address
# def update_cart_price_fields(order):
# pass
def update_sell_data(order):
pass
def update_quantity(order):
pass
@receiver(post_save, sender=OrderModel)
def create_shop_orders_on_payment(sender, instance: OrderModel, created, **kwargs):
"""When an order becomes paid, split it into per-shop ShopOrderModel records.
This handler is safe to run multiple times (it checks if shop_orders exist).
It triggers only when `is_paid` is True.
"""
# Only generate when order is paid and we don't already have shop orders
if not instance.is_paid:
return
if instance.shop_orders.exists():
return
# Collect order items grouped by shop
items = instance.items.select_related('product__product__shop')
shop_groups = {}
for item in items:
# product is ProductVariant -> product.product is ProductModel -> shop on ProductModel
shop = None
try:
shop = item.product.product.shop
except Exception:
shop = None
if not shop:
# If product has no shop, skip (or you might want a default platform shop)
continue
shop_groups.setdefault(shop, []).append(item)
if not shop_groups:
return
# Totals for proportional distribution
shop_subtotals = {}
for shop, items_list in shop_groups.items():
subtotal = 0
for it in items_list:
subtotal += int(it.price) * int(it.quantity)
shop_subtotals[shop] = subtotal
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)
# 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
commission_percent = getattr(shop, 'commission_percent', 0) or 0
try:
commission_percent_value = float(commission_percent)
except Exception:
commission_percent_value = 0.0
# commission is calculated on the shop subtotal after discounts
base_for_commission = max(
0, shop_subtotal - allocated_discount - allocated_special_discount)
commission_amount = int(
base_for_commission * (commission_percent_value / 100.0))
payable = shop_subtotal - allocated_discount - \
allocated_special_discount - commission_amount + allocated_tax
# Prepare customer information
customer_phone = instance.user.phone if instance.user else ''
customer_name = instance.user.full_name if instance.user else ''
# Prepare address information (with text backups in case address is deleted)
address_text = ''
address_postal_code = ''
address_phone = ''
address_city = ''
address_province = ''
address_recipient_name = ''
if instance.address:
address_text = instance.address.address
address_postal_code = instance.address.postal_code
address_phone = instance.address.phone
address_city = instance.address.city
address_province = instance.address.province
address_recipient_name = instance.address.name
# Convert Jalali date to datetime if needed
order_created_datetime = None
if instance.created_at:
try:
# If it's already a datetime, use it
if hasattr(instance.created_at, 'hour'):
order_created_datetime = instance.created_at
else:
# If it's a date, convert to datetime at midnight
from datetime import datetime, time
order_created_datetime = datetime.combine(instance.created_at, time.min)
except Exception:
order_created_datetime = None
shop_order = ShopOrderModel.objects.create(
order=instance,
shop=shop,
customer=instance.user,
customer_phone=customer_phone,
customer_name=customer_name,
address=instance.address,
address_text=address_text,
address_postal_code=address_postal_code,
address_phone=address_phone,
address_city=address_city,
address_province=address_province,
address_recipient_name=address_recipient_name,
status=instance.status,
is_paid=instance.is_paid,
subtotal=shop_subtotal,
items_count=sum(int(it.quantity) for it in items_list),
discount_amount=allocated_discount,
special_discount_amount=allocated_special_discount,
commission_percent=commission_percent_value,
commission_amount=commission_amount,
tax_amount=allocated_tax,
payable_amount=payable,
order_created_at=order_created_datetime,
)
# Create ShopOrderItem rows linking to original OrderItemModel
for it in items_list:
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),
)
@receiver(post_save, sender=ShopOrderModel)
def send_invoice_to_shop_telegram(sender, instance: ShopOrderModel, created, **kwargs):
"""Automatically send invoice to shop's Telegram chat when ShopOrderModel is created.
This handler triggers when a new ShopOrderModel is created and the shop has a telegram_chat_id configured.
It sends the invoice PDF asynchronously via Celery task.
"""
if not created:
return
# Check if shop has telegram_chat_id configured
if not instance.shop or not instance.shop.telegram_chat_id:
return
# Get bot token from settings
bot_token = getattr(settings, 'TELEGRAM_BOT_TOKEN', None)
if not bot_token:
return
# Send invoice asynchronously
try:
send_shop_order_invoice_telegram_task.delay(
shop_order_id=instance.pk,
chat_id=instance.shop.telegram_chat_id,
bot_token=bot_token
)
except Exception as e:
send_shop_order_invoice_telegram_task(
shop_order_id=instance.pk,
chat_id=instance.shop.telegram_chat_id,
bot_token=bot_token
)