import logging from contextlib import suppress from datetime import timedelta from typing import Any from django.db import IntegrityError from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.crypto import get_random_string from django.utils.http import urlsafe_base64_decode from django.utils.timezone import now from sentry_sdk import capture_exception from engine.core.crm import any_crm_integrations from engine.core.crm.exceptions import CRMException from engine.core.models import ( Category, DigitalAssetDownload, Order, Product, PromoCode, Wishlist, ) from engine.core.utils import ( generate_human_readable_id, resolve_translations_for_elasticsearch, ) from engine.core.utils.emailing import ( send_order_created_email, send_order_finished_email, send_promocode_created_email, ) from engine.vibes_auth.models import User from schon.utils.misc import create_object logger = logging.getLogger(__name__) # noinspection PyUnusedLocal @receiver(post_save, sender=User) def create_order_on_user_creation_signal( instance: User, created: bool, **kwargs: dict[Any, Any] ) -> None: if created: try: Order.objects.create(user=instance, status="PENDING") except IntegrityError: human_readable_id = generate_human_readable_id() while True: if Order.objects.filter(human_readable_id=human_readable_id).exists(): human_readable_id = generate_human_readable_id() continue Order.objects.create( user=instance, status="PENDING", human_readable_id=human_readable_id ) break # noinspection PyUnusedLocal @receiver(post_save, sender=User) def create_wishlist_on_user_creation_signal( instance: User, created: bool, **kwargs: dict[Any, Any] ) -> None: if created: Wishlist.objects.create(user=instance) # noinspection PyUnusedLocal @receiver(post_save, sender=User) def create_promocode_on_user_referring( instance: User, created: bool, **kwargs: dict[Any, Any] ) -> None: try: if type(instance.attributes) is not dict: instance.attributes = {} instance.save() if created and instance.attributes.get("referrer", ""): referrer_uuid = urlsafe_base64_decode( instance.attributes.get("referrer", "") ).decode() referrer = User.objects.get(uuid=referrer_uuid) code = f"WELCOME-{get_random_string(6)}" PromoCode.objects.create( user=referrer, code=code if len(code) <= 20 else code[:20], discount_percent=10, start_time=now(), end_time=now() + timedelta(days=30), ) except Exception as e: capture_exception(e) logger.error("Could not create PromoCode: %s", str(e)) # noinspection PyUnusedLocal @receiver(post_save, sender=Order) def process_order_changes(instance: Order, created: bool, **kwargs: dict[Any, Any]): if type(instance.attributes) is not dict: instance.attributes = {} if any_crm_integrations() and instance.status != "PENDING": with suppress(CRMException): instance.trigger_crm() if not created: if instance.status != "PENDING" and instance.user: pending_orders = Order.objects.filter(user=instance.user, status="PENDING") if not pending_orders.exists(): try: Order.objects.create(user=instance.user, status="PENDING") except IntegrityError: human_readable_id = generate_human_readable_id() while True: try: if Order.objects.filter( human_readable_id=human_readable_id ).exists(): human_readable_id = generate_human_readable_id() continue Order.objects.create( user=instance, status="PENDING", human_readable_id=human_readable_id, ) break except IntegrityError: continue if instance.status in ["CREATED", "PAYMENT"]: if not instance.is_whole_digital: send_order_created_email.delay(str(instance.uuid)) for order_product in instance.order_products.filter( status="DELIVERING", product__is_digital=True ): if not order_product.product: continue stocks_qs = order_product.product.stocks.filter( digital_asset__isnull=False ).exclude(digital_asset="") stock = stocks_qs.first() has_file = False if stock: f = stock.digital_asset has_file = bool(getattr(f, "name", "")) and f.storage.exists(f.name) if has_file: order_product.status = "FINISHED" DigitalAssetDownload.objects.get_or_create( order_product=order_product ) order_product.order.user.payments_balance.amount -= ( order_product.buy_price ) order_product.order.user.payments_balance.save() order_product.save() continue order_product.save() try: vendor_name = ( order_product.product.stocks.filter( price=order_product.buy_price ) .first() .vendor.name.lower() ) vendor = create_object( f"engine.core.vendors.{vendor_name}", f"{vendor_name.title()}Vendor", ) vendor.buy_order_product(order_product) except Exception as e: order_product.add_error( f"Failed to buy {order_product.uuid}. Reason: {e}..." ) else: instance.finalize() if ( instance.order_products.filter(status="FAILED").count() == instance.order_products.count() ): instance.status = "FAILED" instance.save() if instance.status == "FINISHED" and not instance.attributes.get( "system_email_sent", False ): instance.attributes["system_email_sent"] = True instance.save() send_order_finished_email.delay(str(instance.uuid)) # noinspection PyUnusedLocal @receiver(post_save, sender=Product) def update_product_name_lang( instance: Product, created: bool, **kwargs: dict[Any, Any] ) -> None: if created: pass resolve_translations_for_elasticsearch(instance, "name") resolve_translations_for_elasticsearch(instance, "description") # noinspection PyUnusedLocal @receiver(post_save, sender=Category) def update_category_name_lang( instance: Category, created: bool, **kwargs: dict[Any, Any] ) -> None: if created: pass resolve_translations_for_elasticsearch(instance, "name") resolve_translations_for_elasticsearch(instance, "description") # noinspection PyUnusedLocal @receiver(post_save, sender=PromoCode) def send_promocode_creation_email( instance: PromoCode, created: bool, **kwargs: dict[Any, Any] ) -> None: if created: send_promocode_created_email.delay(str(instance.uuid))