Commit 64d8a379 by zubair-arbi

ENT-1052 apply enterprise offers by provided catalog uuid

parent 979dc88c
...@@ -8,9 +8,12 @@ from slumber.exceptions import SlumberHttpBaseException ...@@ -8,9 +8,12 @@ from slumber.exceptions import SlumberHttpBaseException
from ecommerce.enterprise.api import catalog_contains_course_runs, fetch_enterprise_learner_data from ecommerce.enterprise.api import catalog_contains_course_runs, fetch_enterprise_learner_data
from ecommerce.enterprise.constants import ENTERPRISE_OFFERS_SWITCH from ecommerce.enterprise.constants import ENTERPRISE_OFFERS_SWITCH
from ecommerce.extensions.basket.utils import ENTERPRISE_CATALOG_ATTRIBUTE_TYPE
from ecommerce.extensions.offer.decorators import check_condition_applicability from ecommerce.extensions.offer.decorators import check_condition_applicability
from ecommerce.extensions.offer.mixins import ConditionWithoutRangeMixin, SingleItemConsumptionConditionMixin from ecommerce.extensions.offer.mixins import ConditionWithoutRangeMixin, SingleItemConsumptionConditionMixin
BasketAttribute = get_model('basket', 'BasketAttribute')
BasketAttributeType = get_model('basket', 'BasketAttributeType')
Condition = get_model('offer', 'Condition') Condition = get_model('offer', 'Condition')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -30,6 +33,9 @@ class EnterpriseCustomerCondition(ConditionWithoutRangeMixin, SingleItemConsumpt ...@@ -30,6 +33,9 @@ class EnterpriseCustomerCondition(ConditionWithoutRangeMixin, SingleItemConsumpt
Determines if a user is eligible for an enterprise customer offer Determines if a user is eligible for an enterprise customer offer
based on their association with the enterprise customer. based on their association with the enterprise customer.
It also verifies the catalog `catalog` on the
offer with the catalog on the basket when provided.
Args: Args:
basket (Basket): Contains information about order line items, the current site, basket (Basket): Contains information about order line items, the current site,
and the user attempting to make the purchase. and the user attempting to make the purchase.
...@@ -66,6 +72,13 @@ class EnterpriseCustomerCondition(ConditionWithoutRangeMixin, SingleItemConsumpt ...@@ -66,6 +72,13 @@ class EnterpriseCustomerCondition(ConditionWithoutRangeMixin, SingleItemConsumpt
course_run_ids.append(course.id) course_run_ids.append(course.id)
# Verify that the current conditional offer is related to the provided
# enterprise catalog
catalog = self._get_enterprise_catalog_uuid_from_basket(basket)
if catalog:
if str(offer.condition.enterprise_customer_catalog_uuid) != catalog:
return False
if not catalog_contains_course_runs(basket.site, course_run_ids, self.enterprise_customer_uuid, if not catalog_contains_course_runs(basket.site, course_run_ids, self.enterprise_customer_uuid,
enterprise_customer_catalog_uuid=self.enterprise_customer_catalog_uuid): enterprise_customer_catalog_uuid=self.enterprise_customer_catalog_uuid):
# Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
...@@ -73,3 +86,31 @@ class EnterpriseCustomerCondition(ConditionWithoutRangeMixin, SingleItemConsumpt ...@@ -73,3 +86,31 @@ class EnterpriseCustomerCondition(ConditionWithoutRangeMixin, SingleItemConsumpt
return False return False
return True return True
@staticmethod
def _get_enterprise_catalog_uuid_from_basket(basket):
"""
Helper method for fetching enterprise catalog UUID from basket.
Arguments:
basket (Basket): The provided basket can be either temporary (just
for calculating discounts) or an actual one to buy a product.
"""
# For temporary basket try to get `catalog` from request
catalog = basket.strategy.request.GET.get(
'catalog'
) if basket.strategy.request else None
if not catalog:
# For actual baskets get `catalog` from basket attribute
enterprise_catalog_attribute, __ = BasketAttributeType.objects.get_or_create(
name=ENTERPRISE_CATALOG_ATTRIBUTE_TYPE
)
enterprise_customer_catalog = BasketAttribute.objects.filter(
basket=basket,
attribute_type=enterprise_catalog_attribute,
).first()
if enterprise_customer_catalog:
catalog = enterprise_customer_catalog.value_text
return catalog
from decimal import Decimal from decimal import Decimal
from uuid import uuid4
import httpretty import httpretty
from oscar.core.loading import get_model from oscar.core.loading import get_model
...@@ -7,6 +8,7 @@ from waffle.models import Switch ...@@ -7,6 +8,7 @@ from waffle.models import Switch
from ecommerce.courses.tests.factories import CourseFactory from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.enterprise.constants import ENTERPRISE_OFFERS_SWITCH from ecommerce.enterprise.constants import ENTERPRISE_OFFERS_SWITCH
from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin
from ecommerce.extensions.basket.utils import basket_add_enterprise_catalog_attribute
from ecommerce.extensions.catalogue.tests.mixins import DiscoveryTestMixin from ecommerce.extensions.catalogue.tests.mixins import DiscoveryTestMixin
from ecommerce.extensions.test import factories from ecommerce.extensions.test import factories
from ecommerce.tests.factories import ProductFactory, SiteConfigurationFactory from ecommerce.tests.factories import ProductFactory, SiteConfigurationFactory
...@@ -50,6 +52,61 @@ class EnterpriseCustomerConditionTests(EnterpriseServiceMockMixin, DiscoveryTest ...@@ -50,6 +52,61 @@ class EnterpriseCustomerConditionTests(EnterpriseServiceMockMixin, DiscoveryTest
) )
self.assertTrue(self.condition.is_satisfied(offer, basket)) self.assertTrue(self.condition.is_satisfied(offer, basket))
def _check_condition_is_satisfied(self, offer, basket, is_satisfied):
"""
Helper method to verify that conditional offer is valid for provided basket.
"""
basket.add_product(self.course_run.seat_products[0])
self.mock_enterprise_learner_api(
learner_id=self.user.id,
enterprise_customer_uuid=str(self.condition.enterprise_customer_uuid),
course_run_id=self.course_run.id,
)
self.mock_catalog_contains_course_runs(
[self.course_run.id],
self.condition.enterprise_customer_uuid,
enterprise_customer_catalog_uuid=self.condition.enterprise_customer_catalog_uuid,
)
assert is_satisfied == self.condition.is_satisfied(offer, basket)
@httpretty.activate
def test_is_satisfied_true_for_enterprise_catalog_in_get_request(self):
"""
Ensure that condition returns true for valid enterprise catalog uuid in GET request.
"""
offer = factories.EnterpriseOfferFactory(site=self.site, condition=self.condition)
enterprise_catalog_uuid = str(self.condition.enterprise_customer_catalog_uuid)
basket = factories.BasketFactory(site=self.site, owner=self.user)
basket.strategy.request = self.request
basket.strategy.request.GET = {'catalog': enterprise_catalog_uuid}
self._check_condition_is_satisfied(offer, basket, is_satisfied=True)
@httpretty.activate
def test_is_satisfied_true_for_enterprise_catalog_in_basket_attribute(self):
"""
Ensure that condition returns true for valid enterprise catalog uuid in basket attribute.
"""
offer = factories.EnterpriseOfferFactory(site=self.site, condition=self.condition)
enterprise_catalog_uuid = str(self.condition.enterprise_customer_catalog_uuid)
basket = factories.BasketFactory(site=self.site, owner=self.user)
request_data = {'catalog': enterprise_catalog_uuid}
basket_add_enterprise_catalog_attribute(basket, request_data)
self._check_condition_is_satisfied(offer, basket, is_satisfied=True)
@httpretty.activate
def test_is_satisfied_false_for_invalid_enterprise_catalog(self):
"""
Ensure the condition returns false if provide enterprise catalog UUID.
"""
offer = factories.EnterpriseOfferFactory(site=self.site, condition=self.condition)
invalid_enterprise_catalog_uuid = str(uuid4())
basket = factories.BasketFactory(site=self.site, owner=self.user)
basket.strategy.request = self.request
basket.strategy.request.GET = {'catalog': invalid_enterprise_catalog_uuid}
self._check_condition_is_satisfied(offer, basket, is_satisfied=False)
assert invalid_enterprise_catalog_uuid != offer.condition.enterprise_customer_catalog_uuid
@httpretty.activate @httpretty.activate
def test_is_satisfied_for_anonymous_user(self): def test_is_satisfied_for_anonymous_user(self):
""" Ensure the condition returns false for an anonymous user. """ """ Ensure the condition returns false for an anonymous user. """
......
...@@ -350,7 +350,7 @@ class BasketCalculateView(generics.GenericAPIView): ...@@ -350,7 +350,7 @@ class BasketCalculateView(generics.GenericAPIView):
# This is to avoid merging this temporary basket with a real user basket. # This is to avoid merging this temporary basket with a real user basket.
with transaction.atomic(): with transaction.atomic():
basket = Basket(owner=user, site=request.site) basket = Basket(owner=user, site=request.site)
basket.strategy = Selector().strategy(user=user) basket.strategy = Selector().strategy(user=user, request=request)
for product in products: for product in products:
basket.add_product(product, 1) basket.add_product(product, 1)
......
import datetime import datetime
import json import json
from uuid import uuid4
import ddt import ddt
import httpretty import httpretty
...@@ -16,6 +17,7 @@ from ecommerce.courses.tests.factories import CourseFactory ...@@ -16,6 +17,7 @@ from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.entitlements.utils import create_or_update_course_entitlement from ecommerce.entitlements.utils import create_or_update_course_entitlement
from ecommerce.extensions.basket.tests.mixins import BasketMixin from ecommerce.extensions.basket.tests.mixins import BasketMixin
from ecommerce.extensions.basket.utils import ( from ecommerce.extensions.basket.utils import (
ENTERPRISE_CATALOG_ATTRIBUTE_TYPE,
add_utm_params_to_url, add_utm_params_to_url,
attribute_cookie_data, attribute_cookie_data,
get_basket_switch_data, get_basket_switch_data,
...@@ -417,6 +419,37 @@ class BasketUtilsTests(DiscoveryTestMixin, BasketMixin, TestCase): ...@@ -417,6 +419,37 @@ class BasketUtilsTests(DiscoveryTestMixin, BasketMixin, TestCase):
# Verify that no exception is raised when no basket attribute exists fitting the delete statement parameters # Verify that no exception is raised when no basket attribute exists fitting the delete statement parameters
prepare_basket(request, [product]) prepare_basket(request, [product])
def test_prepare_basket_with_enterprise_catalog(self):
"""
Test `prepare_basket` with enterprise catalog.
"""
product = ProductFactory()
request = self.request
expected_enterprise_catalog_uuid = str(uuid4())
request.GET = {'catalog': expected_enterprise_catalog_uuid}
basket = prepare_basket(request, [product])
# Verify that the enterprise catalog attribute exists for the basket
# when basket is prepared with the value of provide catalog UUID
enterprise_catalog_uuid = BasketAttribute.objects.get(
basket=basket,
attribute_type__name=ENTERPRISE_CATALOG_ATTRIBUTE_TYPE
).value_text
assert expected_enterprise_catalog_uuid == enterprise_catalog_uuid
# Now verify that `prepare_basket` method removes the enterprise
# catalog attribute if there is not catalog in url
request.GET = {}
basket = prepare_basket(request, [product])
# Verify that the enterprise catalog attribute exists for the basket
# when basket is prepared with the value of provide catalog UUID
with self.assertRaises(BasketAttribute.DoesNotExist):
BasketAttribute.objects.get(
basket=basket,
attribute_type__name=ENTERPRISE_CATALOG_ATTRIBUTE_TYPE
)
def test_basket_switch_data(self): def test_basket_switch_data(self):
"""Verify the correct basket switch data (single vs. multi quantity) is retrieved.""" """Verify the correct basket switch data (single vs. multi quantity) is retrieved."""
__, seat, enrollment_code = self.prepare_course_seat_and_enrollment_code() __, seat, enrollment_code = self.prepare_course_seat_and_enrollment_code()
......
...@@ -3,6 +3,7 @@ import json ...@@ -3,6 +3,7 @@ import json
import logging import logging
from urllib import unquote, urlencode from urllib import unquote, urlencode
import newrelic.agent
import pytz import pytz
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
...@@ -22,6 +23,7 @@ BasketAttribute = get_model('basket', 'BasketAttribute') ...@@ -22,6 +23,7 @@ BasketAttribute = get_model('basket', 'BasketAttribute')
BasketAttributeType = get_model('basket', 'BasketAttributeType') BasketAttributeType = get_model('basket', 'BasketAttributeType')
BUNDLE = 'bundle_identifier' BUNDLE = 'bundle_identifier'
ORGANIZATION_ATTRIBUTE_TYPE = 'organization' ORGANIZATION_ATTRIBUTE_TYPE = 'organization'
ENTERPRISE_CATALOG_ATTRIBUTE_TYPE = 'enterprise_catalog_uuid'
StockRecord = get_model('partner', 'StockRecord') StockRecord = get_model('partner', 'StockRecord')
OrderLine = get_model('order', 'Line') OrderLine = get_model('order', 'Line')
Refund = get_model('refund', 'Refund') Refund = get_model('refund', 'Refund')
...@@ -59,6 +61,7 @@ def prepare_basket(request, products, voucher=None): ...@@ -59,6 +61,7 @@ def prepare_basket(request, products, voucher=None):
basket (Basket): Contains the product to be redeemed and the Voucher applied. basket (Basket): Contains the product to be redeemed and the Voucher applied.
""" """
basket = Basket.get_basket(request.user, request.site) basket = Basket.get_basket(request.user, request.site)
basket_add_enterprise_catalog_attribute(basket, request.GET)
basket.flush() basket.flush()
basket.save() basket.save()
basket_addition = get_class('basket.signals', 'basket_addition') basket_addition = get_class('basket.signals', 'basket_addition')
...@@ -267,6 +270,35 @@ def basket_add_organization_attribute(basket, request_data): ...@@ -267,6 +270,35 @@ def basket_add_organization_attribute(basket, request_data):
) )
@newrelic.agent.function_trace()
def basket_add_enterprise_catalog_attribute(basket, request_data):
"""
Add enterprise catalog UUID attribute on basket, if the catalog UUID value
is provided in the request.
Arguments:
basket(Basket): order basket
request_data (dict): HttpRequest data
"""
# Value of enterprise catalog UUID is being passed as `catalog` from
# basket page
enterprise_catalog_uuid = request_data.get('catalog') if request_data else None
enterprise_catalog_attribute, __ = BasketAttributeType.objects.get_or_create(
name=ENTERPRISE_CATALOG_ATTRIBUTE_TYPE
)
if enterprise_catalog_uuid:
BasketAttribute.objects.update_or_create(
basket=basket,
attribute_type=enterprise_catalog_attribute,
defaults={
'value_text': enterprise_catalog_uuid.strip()}
)
else:
# Remove the enterprise catalog attribute for future update in basket
BasketAttribute.objects.filter(basket=basket, attribute_type=enterprise_catalog_attribute).delete()
def _set_basket_bundle_status(bundle, basket): def _set_basket_bundle_status(bundle, basket):
""" """
Sets the basket's bundle status Sets the basket's bundle status
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment