Commit 75531b2f by Marko Jevtic

[LEARNER-437] Reflect discount on the Program About Page (WL)

parent 5a919f2c
......@@ -130,7 +130,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[unicode(prof_course.id)])
response = self.client.get(url)
self.assertRedirects(response, 'http://testserver/test_basket/?sku=TEST', fetch_redirect_response=False)
self.assertRedirects(response, 'http://testserver/basket/add/?sku=TEST', fetch_redirect_response=False)
ecomm_test_utils.update_commerce_config(enabled=False)
def _generate_enterprise_learner_context(self, enable_audit_enrollment=False):
......
......@@ -15,6 +15,7 @@ class CommerceConfiguration(ConfigurationModel):
API_NAME = 'commerce'
CACHE_KEY = 'commerce.api.data'
DEFAULT_RECEIPT_PAGE_URL = '/checkout/receipt/?order_number='
MULTIPLE_ITEMS_BASKET_PAGE_URL = '/basket/add/'
checkout_on_ecommerce_service = models.BooleanField(
default=False,
......
......@@ -97,7 +97,9 @@ class EcommerceServiceTests(TestCase):
def test_get_checkout_page_url(self, skus):
""" Verify the checkout page URL is properly constructed and returned. """
url = EcommerceService().get_checkout_page_url(*skus)
expected_url = '{root}/test_basket/?{skus}'.format(
config = CommerceConfiguration.current()
expected_url = '{root}{basket_url}?{skus}'.format(
basket_url=config.MULTIPLE_ITEMS_BASKET_PAGE_URL,
root=settings.ECOMMERCE_PUBLIC_URL_ROOT,
skus=urlencode({'sku': skus}, doseq=True),
)
......
......@@ -87,9 +87,9 @@ class EcommerceService(object):
Absolute path to the ecommerce checkout page showing basket that contains specified products.
Example:
http://localhost:8002/basket/single_item/?sku=5H3HG5&sku=57FHHD
http://localhost:8002/basket/add/?sku=5H3HG5&sku=57FHHD
"""
return '{checkout_page_path}?{skus}'.format(
checkout_page_path=self.get_absolute_ecommerce_url(self.config.single_course_checkout_page),
checkout_page_path=self.get_absolute_ecommerce_url(self.config.MULTIPLE_ITEMS_BASKET_PAGE_URL),
skus=urlencode({'sku': skus}, doseq=True),
)
......@@ -313,14 +313,10 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase):
def test_ecommerce_checkout_redirect(self):
"""Verify the block link redirects to ecommerce checkout if it's enabled."""
sku = 'TESTSKU'
checkout_page = '/test_basket/'
CommerceConfiguration.objects.create(
checkout_on_ecommerce_service=True,
single_course_checkout_page=checkout_page
)
configuration = CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True)
self.setup_course_and_user(sku=sku)
block = VerifiedUpgradeDeadlineDate(self.course, self.user)
self.assertEqual(block.link, '{}?sku={}'.format(checkout_page, sku))
self.assertEqual(block.link, '{}?sku={}'.format(configuration.MULTIPLE_ITEMS_BASKET_PAGE_URL, sku))
## VerificationDeadlineDate
def test_no_verification_deadline(self):
......
......@@ -469,12 +469,8 @@ class ViewsTestCase(ModuleStoreTestCase):
_id(bool): Tell the method to either expect an id in the href or not.
"""
checkout_page = '/test_basket/'
sku = 'TEST123'
CommerceConfiguration.objects.create(
checkout_on_ecommerce_service=True,
single_course_checkout_page=checkout_page
)
configuration = CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True)
course = CourseFactory.create()
CourseModeFactory(mode_slug=CourseMode.PROFESSIONAL, course_id=course.id, sku=sku, min_price=1)
......@@ -486,7 +482,10 @@ class ViewsTestCase(ModuleStoreTestCase):
# Construct the link according the following scenarios and verify its presence in the response:
# (1) shopping cart is enabled and the user is not logged in
# (2) shopping cart is enabled and the user is logged in
href = '<a href="{uri_stem}?sku={sku}" class="add-to-cart">'.format(uri_stem=checkout_page, sku=sku)
href = '<a href="{uri_stem}?sku={sku}" class="add-to-cart">'.format(
uri_stem=configuration.MULTIPLE_ITEMS_BASKET_PAGE_URL,
sku=sku,
)
# Generate the course about page content
response = self.client.get(reverse('about_course', args=[unicode(course.id)]))
......
......@@ -131,7 +131,6 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
)
def test_start_flow_with_ecommerce(self):
"""Verify user gets redirected to ecommerce checkout when ecommerce checkout is enabled."""
checkout_page = '/test_basket/'
sku = 'TESTSKU'
# When passing a SKU ecommerce api gets called.
httpretty.register_uri(
......@@ -140,11 +139,10 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
body=json.dumps(['foo', 'bar']),
content_type="application/json",
)
configuration = CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True)
checkout_page = configuration.MULTIPLE_ITEMS_BASKET_PAGE_URL
httpretty.register_uri(httpretty.GET, "{}{}".format(TEST_PUBLIC_URL_ROOT, checkout_page))
CommerceConfiguration.objects.create(
checkout_on_ecommerce_service=True,
single_course_checkout_page=checkout_page
)
course = self._create_course('verified', sku=sku)
self._enroll(course.id)
response = self._get_page('verify_student_start_flow', course.id, expected_status_code=302)
......
......@@ -1085,6 +1085,26 @@
}
}
}
.description {
display: block;
float: left;
}
.price {
.green-highlight {
font-weight: 700;
color: palette(success, text);
}
.original-price {
text-decoration: line-through;
}
.savings {
display: block;
}
}
}
}
......
......@@ -69,7 +69,7 @@ description_max_length = 250
<a href="${buy_button_href}" class="btn-brand btn-large hidden-sm btn-start">
${Text(_('Buy the {program_name} (${price} USD)')).format(
program_name=title,
price=program['price_ranges'][0]['total']
price=full_program_price
)}
</a>
% else:
......@@ -99,7 +99,7 @@ description_max_length = 250
<div class="col col-12 md-col-7 lg-col-8 left-col">
<ul class="list-divided program-desc-tbl">
<li class="item">
<span>
<span class="description">
<span class="fa fa-book fa-lg" aria-hidden="true"></span>
${_('Number of Courses')}
</span>
......@@ -110,7 +110,7 @@ description_max_length = 250
</a>
</li>
<li class="item">
<span>
<span class="description">
<span class="fa fa-clock-o fa-lg" aria-hidden="true"></span>
${_('Average Length')}
</span>
......@@ -121,7 +121,7 @@ description_max_length = 250
</span>
</li>
<li class="item">
<span>
<span class="description">
<span class="fa fa-tachometer fa-lg" aria-hidden="true"></span>
${_('Effort')}
</span>
......@@ -133,15 +133,36 @@ description_max_length = 250
</span>
</li>
<li class="item">
<span>
<span class="description">
<span class="fa fa-tag fa-lg" aria-hidden="true"></span>
${_('Price (USD)')}
</span>
<span>
${Text(_('${full_program_price} for entire program')).format(
full_program_price=full_program_price
)}
</span>
% if program.get('discount_data') and program['discount_data']['is_discounted']:
<span class="price">
<span role="group" aria-label="${_('Original Price')}" class="original-price">
${Text(_('${oldPrice}')).format(
oldPrice=full_program_price_format.format(program['discount_data']['total_incl_tax_excl_discounts'])
)}
</span>
<span role="group" aria-label="${_('Discounted Price')}" class="discount green-highlight">
${Text(_('${newPrice}{htmlEnd} for entire program')).format(
newPrice=full_program_price,
htmlEnd=HTML('</span>')
)}
<span class="savings green-highlight">
${Text(_('You save ${discount_value} {currency}')).format(
discount_value=full_program_price_format.format(program['discount_data']['discount_value']),
currency=program['discount_data']['currency']
)}
</span>
</span>
% else:
<span>
${Text(_('${full_program_price} for entire program')).format(
full_program_price=full_program_price
)}
</span>
% endif
</li>
</ul>
<div id="accordion-group">
......
......@@ -872,6 +872,19 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self.program['applicable_seat_types'] = [seat['type']]
return seat
def _update_discount_data(self, mock_discount_data):
"""
Helper method that updates mocked discount data with
- a flag indicating whether the program price is discounted
- the amount of the discount (0 in case there's no discount)
"""
program_discounted_price = mock_discount_data['total_incl_tax']
program_full_price = mock_discount_data['total_incl_tax_excl_discounts']
mock_discount_data.update({
'is_discounted': program_discounted_price < program_full_price,
'discount_value': program_full_price - program_discounted_price
})
def test_instructors(self):
data = ProgramMarketingDataExtender(self.program, self.user).extend()
......@@ -887,10 +900,13 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self.assertEqual(data['avg_price_per_course'], program_full_price / self.number_of_courses)
def test_course_pricing_when_all_course_runs_have_no_seats(self):
course = ModuleStoreCourseFactory()
course = self.update_course(course, self.user.id)
course_run = CourseRunFactory(key=unicode(course.id), seats=[])
program = ProgramFactory(courses=[CourseFactory(course_runs=[course_run])])
# Create three seatless course runs and add them to the program
course_runs = []
for __ in range(3):
course = ModuleStoreCourseFactory()
course = self.update_course(course, self.user.id)
course_runs.append(CourseRunFactory(key=unicode(course.id), seats=[]))
program = ProgramFactory(courses=[CourseFactory(course_runs=course_runs)])
data = ProgramMarketingDataExtender(program, self.user).extend()
......@@ -988,7 +1004,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self._prepare_program_for_discounted_price_calculation_endpoint()
mock_discount_data = {
'total_incl_tax_excl_discounts': 200.0,
'currency': "USD",
'currency': 'USD',
'total_incl_tax': 50.0
}
httpretty.register_uri(
......@@ -999,6 +1015,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
)
data = ProgramMarketingDataExtender(self.program, self.user).extend()
self._update_discount_data(mock_discount_data)
self.assertEqual(
data['skus'],
......@@ -1015,7 +1032,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
self._prepare_program_for_discounted_price_calculation_endpoint()
mock_discount_data = {
'total_incl_tax_excl_discounts': 200.0,
'currency': "USD",
'currency': 'USD',
'total_incl_tax': 50.0
}
httpretty.register_uri(
......@@ -1026,6 +1043,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
)
data = ProgramMarketingDataExtender(self.program, AnonymousUserFactory()).extend()
self._update_discount_data(mock_discount_data)
self.assertEqual(
data['skus'],
......
......@@ -605,7 +605,7 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
published_course_runs = filter(lambda run: run['status'] == 'published', course['course_runs'])
if len(published_course_runs) == 1:
for seat in published_course_runs[0]['seats']:
if seat['type'] in applicable_seat_types:
if seat['type'] in applicable_seat_types and seat['sku']:
skus.append(seat['sku'])
else:
# If a course in the program has more than 1 published course run
......@@ -626,6 +626,11 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
# Make an API call to calculate the discounted price
discount_data = api.baskets.calculate.get(sku=skus)
program_discounted_price = discount_data['total_incl_tax']
program_full_price = discount_data['total_incl_tax_excl_discounts']
discount_data['is_discounted'] = program_discounted_price < program_full_price
discount_data['discount_value'] = program_full_price - program_discounted_price
self.data.update({
'discount_data': discount_data,
'full_program_price': discount_data['total_incl_tax']
......
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