Commit bec2e16f by Douglas Hall Committed by Douglas Hall

Add ability to apply a voucher to a basket with an Enterprise Offer.

Voucher-based offers should take precedence over any other offers
that may be valid for a given basket. This change will set the
priority on new ConditionalOffers higher than Enterprise offers
or Program offers.

ENT-734
parent 633ea8f0
...@@ -8,6 +8,7 @@ from oscar.core.loading import get_model ...@@ -8,6 +8,7 @@ from oscar.core.loading import get_model
from ecommerce.enterprise.conditions import EnterpriseCustomerCondition from ecommerce.enterprise.conditions import EnterpriseCustomerCondition
from ecommerce.enterprise.constants import BENEFIT_MAP, BENEFIT_TYPE_CHOICES from ecommerce.enterprise.constants import BENEFIT_MAP, BENEFIT_TYPE_CHOICES
from ecommerce.enterprise.utils import get_enterprise_customer from ecommerce.enterprise.utils import get_enterprise_customer
from ecommerce.extensions.offer.models import OFFER_PRIORITY_ENTERPRISE
from ecommerce.programs.custom import class_path, create_condition from ecommerce.programs.custom import class_path, create_condition
Benefit = get_model('offer', 'Benefit') Benefit = get_model('offer', 'Benefit')
...@@ -101,7 +102,7 @@ class EnterpriseOfferForm(forms.ModelForm): ...@@ -101,7 +102,7 @@ class EnterpriseOfferForm(forms.ModelForm):
self.instance.offer_type = ConditionalOffer.SITE self.instance.offer_type = ConditionalOffer.SITE
self.instance.max_basket_applications = 1 self.instance.max_basket_applications = 1
self.instance.site = site self.instance.site = site
self.instance.priority = 10 # This will ensure that Enterprise Offers are applied before Program Offers. self.instance.priority = OFFER_PRIORITY_ENTERPRISE
if commit: if commit:
benefit = getattr(self.instance, 'benefit', Benefit()) benefit = getattr(self.instance, 'benefit', Benefit())
......
...@@ -7,6 +7,7 @@ from oscar.core.loading import get_model ...@@ -7,6 +7,7 @@ from oscar.core.loading import get_model
from ecommerce.enterprise.constants import BENEFIT_MAP from ecommerce.enterprise.constants import BENEFIT_MAP
from ecommerce.enterprise.forms import EnterpriseOfferForm from ecommerce.enterprise.forms import EnterpriseOfferForm
from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin from ecommerce.enterprise.tests.mixins import EnterpriseServiceMockMixin
from ecommerce.extensions.offer.models import OFFER_PRIORITY_ENTERPRISE
from ecommerce.extensions.test import factories from ecommerce.extensions.test import factories
from ecommerce.programs.custom import class_path from ecommerce.programs.custom import class_path
from ecommerce.tests.testcases import TestCase from ecommerce.tests.testcases import TestCase
...@@ -36,6 +37,7 @@ class EnterpriseOfferFormTests(EnterpriseServiceMockMixin, TestCase): ...@@ -36,6 +37,7 @@ class EnterpriseOfferFormTests(EnterpriseServiceMockMixin, TestCase):
self.assertEqual(offer.status, ConditionalOffer.OPEN) self.assertEqual(offer.status, ConditionalOffer.OPEN)
self.assertEqual(offer.max_basket_applications, 1) self.assertEqual(offer.max_basket_applications, 1)
self.assertEqual(offer.site, self.site) self.assertEqual(offer.site, self.site)
self.assertEqual(offer.priority, OFFER_PRIORITY_ENTERPRISE)
self.assertEqual(offer.condition.enterprise_customer_uuid, enterprise_customer_uuid) self.assertEqual(offer.condition.enterprise_customer_uuid, enterprise_customer_uuid)
self.assertEqual(offer.condition.enterprise_customer_name, enterprise_customer_name) self.assertEqual(offer.condition.enterprise_customer_name, enterprise_customer_name)
self.assertEqual(offer.condition.enterprise_customer_catalog_uuid, enterprise_customer_catalog_uuid) self.assertEqual(offer.condition.enterprise_customer_catalog_uuid, enterprise_customer_catalog_uuid)
......
...@@ -19,6 +19,10 @@ from threadlocals.threadlocals import get_current_request ...@@ -19,6 +19,10 @@ from threadlocals.threadlocals import get_current_request
from ecommerce.core.utils import get_cache_key, log_message_and_raise_validation_error from ecommerce.core.utils import get_cache_key, log_message_and_raise_validation_error
OFFER_PRIORITY_ENTERPRISE = 10
OFFER_PRIORITY_VOUCHER = 20
class Benefit(AbstractBenefit): class Benefit(AbstractBenefit):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.clean() self.clean()
......
...@@ -9,6 +9,7 @@ from oscar.test.factories import * # pylint:disable=wildcard-import,unused-wild ...@@ -9,6 +9,7 @@ from oscar.test.factories import * # pylint:disable=wildcard-import,unused-wild
from ecommerce.enterprise.benefits import EnterpriseAbsoluteDiscountBenefit, EnterprisePercentageDiscountBenefit from ecommerce.enterprise.benefits import EnterpriseAbsoluteDiscountBenefit, EnterprisePercentageDiscountBenefit
from ecommerce.enterprise.conditions import EnterpriseCustomerCondition from ecommerce.enterprise.conditions import EnterpriseCustomerCondition
from ecommerce.extensions.offer.models import OFFER_PRIORITY_VOUCHER
from ecommerce.programs.benefits import AbsoluteDiscountBenefitWithoutRange, PercentageDiscountBenefitWithoutRange from ecommerce.programs.benefits import AbsoluteDiscountBenefitWithoutRange, PercentageDiscountBenefitWithoutRange
from ecommerce.programs.conditions import ProgramCourseRunSeatsCondition from ecommerce.programs.conditions import ProgramCourseRunSeatsCondition
from ecommerce.programs.custom import class_path from ecommerce.programs.custom import class_path
...@@ -104,7 +105,8 @@ def prepare_voucher(code='COUPONTEST', _range=None, start_datetime=None, end_dat ...@@ -104,7 +105,8 @@ def prepare_voucher(code='COUPONTEST', _range=None, start_datetime=None, end_dat
benefit=benefit, benefit=benefit,
condition=condition, condition=condition,
max_global_applications=max_usage, max_global_applications=max_usage,
email_domains=email_domains email_domains=email_domains,
priority=OFFER_PRIORITY_VOUCHER
) )
else: else:
offer = ConditionalOfferFactory( offer = ConditionalOfferFactory(
...@@ -112,7 +114,8 @@ def prepare_voucher(code='COUPONTEST', _range=None, start_datetime=None, end_dat ...@@ -112,7 +114,8 @@ def prepare_voucher(code='COUPONTEST', _range=None, start_datetime=None, end_dat
benefit=benefit, benefit=benefit,
condition=condition, condition=condition,
email_domains=email_domains, email_domains=email_domains,
site=site site=site,
priority=OFFER_PRIORITY_VOUCHER
) )
voucher.offers.add(offer) voucher.offers.add(offer)
return voucher, product return voucher, product
......
...@@ -20,6 +20,7 @@ from ecommerce.extensions.api import exceptions ...@@ -20,6 +20,7 @@ from ecommerce.extensions.api import exceptions
from ecommerce.extensions.catalogue.tests.mixins import DiscoveryTestMixin from ecommerce.extensions.catalogue.tests.mixins import DiscoveryTestMixin
from ecommerce.extensions.fulfillment.modules import CouponFulfillmentModule from ecommerce.extensions.fulfillment.modules import CouponFulfillmentModule
from ecommerce.extensions.fulfillment.status import LINE from ecommerce.extensions.fulfillment.status import LINE
from ecommerce.extensions.offer.models import OFFER_PRIORITY_VOUCHER
from ecommerce.extensions.test.factories import create_order, prepare_voucher from ecommerce.extensions.test.factories import create_order, prepare_voucher
from ecommerce.extensions.voucher.utils import ( from ecommerce.extensions.voucher.utils import (
create_vouchers, create_vouchers,
...@@ -199,6 +200,7 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM ...@@ -199,6 +200,7 @@ class UtilTests(CouponMixin, DiscoveryMockMixin, DiscoveryTestMixin, LmsApiMockM
self.assertEqual(voucher_offer.benefit.value, 100.00) self.assertEqual(voucher_offer.benefit.value, 100.00)
self.assertEqual(voucher_offer.benefit.range.catalog, self.catalog) self.assertEqual(voucher_offer.benefit.range.catalog, self.catalog)
self.assertEqual(voucher_offer.email_domains, email_domains) self.assertEqual(voucher_offer.email_domains, email_domains)
self.assertEqual(voucher_offer.priority, OFFER_PRIORITY_VOUCHER)
self.assertEqual(len(coupon_voucher.vouchers.all()), 11) self.assertEqual(len(coupon_voucher.vouchers.all()), 11)
self.assertEqual(voucher.end_datetime, self.data['end_datetime']) self.assertEqual(voucher.end_datetime, self.data['end_datetime'])
self.assertEqual(voucher.start_datetime, self.data['start_datetime']) self.assertEqual(voucher.start_datetime, self.data['start_datetime'])
......
...@@ -19,6 +19,7 @@ from oscar.templatetags.currency_filters import currency ...@@ -19,6 +19,7 @@ from oscar.templatetags.currency_filters import currency
from ecommerce.core.url_utils import get_ecommerce_url from ecommerce.core.url_utils import get_ecommerce_url
from ecommerce.core.utils import log_message_and_raise_validation_error from ecommerce.core.utils import log_message_and_raise_validation_error
from ecommerce.extensions.api import exceptions from ecommerce.extensions.api import exceptions
from ecommerce.extensions.offer.models import OFFER_PRIORITY_VOUCHER
from ecommerce.extensions.offer.utils import get_discount_percentage, get_discount_value from ecommerce.extensions.offer.utils import get_discount_percentage, get_discount_value
from ecommerce.invoice.models import Invoice from ecommerce.invoice.models import Invoice
from ecommerce.programs.conditions import ProgramCourseRunSeatsCondition from ecommerce.programs.conditions import ProgramCourseRunSeatsCondition
...@@ -377,7 +378,8 @@ def _get_or_create_offer( ...@@ -377,7 +378,8 @@ def _get_or_create_offer(
benefit=offer_benefit, benefit=offer_benefit,
max_global_applications=max_uses, max_global_applications=max_uses,
email_domains=email_domains, email_domains=email_domains,
site=site site=site,
priority=OFFER_PRIORITY_VOUCHER,
) )
return offer return offer
......
...@@ -2,6 +2,7 @@ from decimal import Decimal ...@@ -2,6 +2,7 @@ from decimal import Decimal
import httpretty import httpretty
from oscar.core.loading import get_class from oscar.core.loading import get_class
from oscar.test.factories import RangeFactory
from ecommerce.courses.models import Course from ecommerce.courses.models import Course
from ecommerce.extensions.partner.strategy import DefaultStrategy from ecommerce.extensions.partner.strategy import DefaultStrategy
...@@ -29,10 +30,12 @@ class ProgramOfferTests(ProgramTestMixin, TestCase): ...@@ -29,10 +30,12 @@ class ProgramOfferTests(ProgramTestMixin, TestCase):
self.mock_enrollment_api(basket.owner.username) self.mock_enrollment_api(basket.owner.username)
# Add one course run seat from each course to the basket. # Add one course run seat from each course to the basket.
products = []
for course in program['courses']: for course in program['courses']:
course_run = Course.objects.get(id=course['course_runs'][0]['key']) course_run = Course.objects.get(id=course['course_runs'][0]['key'])
for seat in course_run.seat_products: for seat in course_run.seat_products:
if seat.attr.id_verification_required: if seat.attr.id_verification_required:
products.append(seat)
basket.add_product(seat) basket.add_product(seat)
# No discounts should be applied, and each line should have a price of 100.00. # No discounts should be applied, and each line should have a price of 100.00.
...@@ -51,3 +54,17 @@ class ProgramOfferTests(ProgramTestMixin, TestCase): ...@@ -51,3 +54,17 @@ class ProgramOfferTests(ProgramTestMixin, TestCase):
self.assertEqual(basket.total_discount, Decimal(100) * len(lines)) self.assertEqual(basket.total_discount, Decimal(100) * len(lines))
for line in lines: for line in lines:
self.assertEqual(line.line_price_incl_tax_incl_discounts, 0) self.assertEqual(line.line_price_incl_tax_incl_discounts, 0)
# Reset the basket and add a voucher.
basket.reset_offer_applications()
product_range = RangeFactory(products=products)
voucher, __ = factories.prepare_voucher(_range=product_range, benefit_value=50)
basket.vouchers.add(voucher)
# Apply offers and verify that voucher-based offer takes precedence over program offer
Applicator().apply(basket, basket.owner)
lines = basket.all_lines()
self.assertEqual(len(basket.offer_applications), 1)
self.assertEqual(basket.total_discount, Decimal(50) * len(lines))
for line in lines:
self.assertEqual(line.line_price_incl_tax_incl_discounts, 50)
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