diff --git a/core/crm/amo/gateway.py b/core/crm/amo/gateway.py index e699bd8c..b8b0d272 100644 --- a/core/crm/amo/gateway.py +++ b/core/crm/amo/gateway.py @@ -12,13 +12,13 @@ from core.models import CustomerRelationshipManagementProvider, Order, OrderCrmL logger = logging.getLogger("django") -class AmoOrderGateway: +class AmoCRM: def __init__(self): try: self.instance = CustomerRelationshipManagementProvider.objects.get(name="amo") - except CustomerRelationshipManagementProvider.DoesNotExist: + except CustomerRelationshipManagementProvider.DoesNotExist as dne: logger.warning("AMO CRM provider not found") - raise CRMException("AMO CRM provider not found") + raise CRMException("AMO CRM provider not found") from dne self.base = f"https://{self.instance.integration_url}" @@ -31,6 +31,8 @@ class AmoOrderGateway: self.stage_map = self.instance.attributes.get("stage_map") self.responsible_user_id = self.instance.attributes.get("responsible_user_id") + self.fns_api_key = self.instance.attributes.get("fns_api_key") + if not all( [ self.base, @@ -39,6 +41,7 @@ class AmoOrderGateway: self.redirect_uri, self.pipeline_id, self.stage_map, + self.fns_api_key, ] ): raise CRMException("AMO CRM provider not configured") @@ -79,7 +82,12 @@ class AmoOrderGateway: def upsert_order(self, order: Order) -> str: with transaction.atomic(): - link: Optional[OrderCrmLink] = OrderCrmLink.objects.filter(order=order).first() + try: + link: Optional[OrderCrmLink] = OrderCrmLink.objects.get(order=order) + except OrderCrmLink.MultipleObjectsReturned: + link = OrderCrmLink.objects.filter(order=order).first() + except OrderCrmLink.DoesNotExist: + link = None if link: lead_id = link.crm_lead_id payload = self._build_lead_payload(order) diff --git a/core/models.py b/core/models.py index 9235865d..4f9f321d 100644 --- a/core/models.py +++ b/core/models.py @@ -1,6 +1,8 @@ import datetime import json import logging +import traceback +from contextlib import suppress from typing import Any, Optional, Self from constance import config @@ -57,6 +59,7 @@ from core.utils.db import TweakedAutoSlugField, unicode_slugify_function from core.utils.lists import FAILED_STATUSES from core.validators import validate_category_image_dimensions from evibes.settings import CURRENCY_CODE +from evibes.utils.misc import create_object from payments.models import Transaction logger = logging.getLogger("django") @@ -1428,9 +1431,13 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi @property def is_business(self) -> bool: - return (self.attributes.get("is_business", False) if self.attributes else False) or ( - self.user.attributes.get("is_business", False) if self.user else False - ) + with suppress(Exception): + return (self.attributes.get("is_business", False) if self.attributes else False) or ( + (self.user.attributes.get("is_business", False) and self.user.attributes.get("business_identificator")) + if self.user + else False + ) + return False def save(self, **kwargs) -> Self: pending_orders = 0 @@ -1776,6 +1783,29 @@ class Order(ExportModelOperationsMixin("order"), NiceModel): # type: ignore [mi ) return self + def trigger_crm(self): + crm_links = OrderCrmLink.objects.filter(order=self) + if crm_links.exists(): + crm_link = crm_links.first() + crm_integration = create_object(crm_link.crm.integration_location, crm_link.crm.name) + try: + crm_integration.upsert_order(self) + return True + except Exception as e: + logger.error(f"failed to trigger crm integration {crm_link.crm.name} for order {self.uuid}: {e}") + logger.error(traceback.format_exc()) + return False + else: + crm = CustomerRelationshipManagementProvider.objects.get(default=True) + crm_integration = create_object(crm.integration_location, crm.name) + try: + crm_integration.upsert_order(self) + return True + except Exception as e: + logger.error(f"failed to trigger crm integration {crm.name} for order {self.uuid}: {e}") + logger.error(traceback.format_exc()) + return False + class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): # type: ignore [misc, django-manager-missing] """ @@ -1935,17 +1965,24 @@ class CustomerRelationshipManagementProvider(ExportModelOperationsMixin("crm_pro integration_url = URLField(blank=True, null=True, help_text=_("URL of the integration")) authentication = JSONField(blank=True, null=True, help_text=_("authentication credentials")) attributes = JSONField(blank=True, null=True, help_name=_("attributes")) + integration_location = CharField(max_length=128, blank=True, null=True) + default = BooleanField(default=False) def __str__(self) -> str: return self.crm_lead_id + def save(self, **kwargs): + if self.objects.filter(default=True).exists(): + raise ValueError(_("you can only have one default CRM provider")) + super().save(**kwargs) + class Meta: verbose_name = _("order CRM link") verbose_name_plural = _("orders CRM links") class OrderCrmLink(ExportModelOperationsMixin("order_crm_link"), NiceModel): - order_uuid = ForeignKey(to=Order, on_delete=PROTECT, related_name="crm_links") + order = ForeignKey(to=Order, on_delete=PROTECT, related_name="crm_links") crm = ForeignKey(to=CustomerRelationshipManagementProvider, on_delete=PROTECT, related_name="order_links") crm_lead_id = CharField(max_length=30, unique=True, db_index=True) diff --git a/core/signals.py b/core/signals.py index 4e040c96..4cd6a12f 100644 --- a/core/signals.py +++ b/core/signals.py @@ -10,6 +10,7 @@ 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.models import Category, Order, Product, PromoCode, Wishlist, DigitalAssetDownload from core.utils import ( generate_human_readable_id, @@ -67,6 +68,9 @@ def process_order_changes(instance, created, **_kwargs): if type(instance.attributes) is not dict: instance.attributes = {} + if any_crm_integrations(): + instance.trigger_crm() + if not created: if instance.status != "PENDING" and instance.user: pending_orders = Order.objects.filter(user=instance.user, status="PENDING") diff --git a/vibes_auth/serializers.py b/vibes_auth/serializers.py index 9f6f59cb..37587902 100644 --- a/vibes_auth/serializers.py +++ b/vibes_auth/serializers.py @@ -104,9 +104,10 @@ class UserSerializer(ModelSerializer): raise ValidationError(_("passwords do not match")) if "phone_number" in attrs: validate_phone_number(attrs["phone_number"]) - if User.objects.filter(phone_number=attrs["phone_number"]).exclude(uuid=self.instance.uuid).exists(): - phone_number = attrs["phone_number"] - raise ValidationError(_(f"malformed phone number: {phone_number}")) + if self.instance: + if User.objects.filter(phone_number=attrs["phone_number"]).exclude(uuid=self.instance.uuid).exists(): + phone_number = attrs["phone_number"] + raise ValidationError(_(f"malformed phone number: {phone_number}")) if "email" in attrs: validate_email(attrs["email"]) if User.objects.filter(email=attrs["email"]).exclude(uuid=self.instance.uuid).exists():