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 or '') if instance.user else '' customer_name = (instance.user.full_name or '') 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 or '' address_postal_code = instance.address.postal_code or '' address_phone = instance.address.phone or '' address_city = instance.address.city or '' address_province = instance.address.province or '' address_recipient_name = instance.address.name or '' # 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 )