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 django.utils.translation import gettext_lazy as _ from sentry_sdk import capture_exception from core.crm import any_crm_integrations from core.crm.exceptions import CRMException from core.models import Category, DigitalAssetDownload, Order, Product, PromoCode, Wishlist from core.utils import ( generate_human_readable_id, resolve_translations_for_elasticsearch, ) from core.utils.emailing import send_order_created_email, send_order_finished_email, send_promocode_created_email from evibes.utils.misc import create_object from vibes_auth.models import User logger = logging.getLogger("django") # 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 not instance.attributes: 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(_(f"error during promocode creation: {e!s}")) # 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(instance.uuid) # type: ignore [attr-defined] 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" if not order_product.download: DigitalAssetDownload.objects.create(order_product=order_product) order_product.order.user.payments_balance.amount -= order_product.buy_price # type: ignore [union-attr, operator] order_product.order.user.payments_balance.save() # type: ignore [union-attr] order_product.save() continue order_product.save() try: vendor_name = ( order_product.product.stocks.filter(price=order_product.buy_price).first().vendor.name.lower() # type: ignore [union-attr, attr-defined, misc] ) vendor = create_object(f"core.vendors.{vendor_name}", f"{vendor_name.title()}Vendor") vendor.buy_order_product(order_product) # type: ignore [attr-defined] 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(instance.uuid) # type: ignore [attr-defined] # 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(instance.uuid) # type: ignore [attr-defined]