Commit ddee6598 by McKenzie Welter Committed by Michael LoTurco

Update program price_ranges property

programs formerly only calculated price ranges based on seats,
this commit changes price ranges to be calculated with the price
of entitlements for a course when available, and falls back to
the price of the seat for the course when no entitlement is present

LEARNER-3297
parent 0036b976
......@@ -1087,6 +1087,11 @@ class Program(TimeStampedModel):
yield seat
@property
def entitlements(self):
applicable_seat_types = set(seat_type.name for seat_type in self.type.applicable_seat_types.all())
return CourseEntitlement.objects.filter(mode__name__in=applicable_seat_types, course__in=self.courses.all())
@property
def seat_types(self):
return set(seat.type for seat in self.seats)
......@@ -1124,7 +1129,7 @@ class Program(TimeStampedModel):
# And use the price of the seat to represent the price of the course
selected_seats = course_map.get(course_uuid)
if not selected_seats:
# if we do not have this course yet, create the seats array
# If we do not have this course yet, create the seats array
course_map[course_uuid] = [seat]
else:
add_seat = False
......@@ -1153,12 +1158,26 @@ class Program(TimeStampedModel):
# Now remove the seats that should not be counted for calculation for program total
course_map[course_uuid].remove(seat)
for entitlement in self.entitlements:
course_uuid = entitlement.course.uuid
selected_seats = course_map.get(course_uuid)
if not selected_seats:
course_map[course_uuid] = [entitlement]
else:
for selected_seat in selected_seats:
if entitlement.currency == selected_seat.currency:
# If the seat in the array has the same currency as the entitlement,
# dont consider it for pricing by remove it from the course_map
course_map[course_uuid].remove(selected_seat)
# Entitlement should be considered for pricing instead of removed seat.
course_map[course_uuid].append(entitlement)
# Now calculate the total price of the program indexed by currency
for course_seats in course_map.values():
for seat in course_seats:
current_total = currencies_with_total.get(seat.currency, 0)
current_total += seat.price
currencies_with_total[seat.currency] = current_total
for course_products in course_map.values():
for product in course_products:
current_total = currencies_with_total.get(product.currency, 0)
current_total += product.price
currencies_with_total[product.currency] = current_total
return currencies_with_total
......@@ -1167,6 +1186,8 @@ class Program(TimeStampedModel):
currencies = defaultdict(list)
for seat in self.canonical_seats:
currencies[seat.currency].append(seat.price)
for entitlement in self.entitlements:
currencies[entitlement.currency].append(entitlement.price)
total_by_currency = self._get_total_price_by_currency()
......
......@@ -854,6 +854,21 @@ class ProgramTests(TestCase):
self.assertEqual(set(course.canonical_course_run.seats.all()), set(program.canonical_seats))
def test_entitlements(self):
""" Test entitlements returns only applicable course entitlements. """
course = factories.CourseFactory()
verified_mode = SeatType.objects.get(name='verified')
credit_mode = SeatType.objects.get(name='credit')
professional_mode = SeatType.objects.get(name='professional')
for mode in [verified_mode, credit_mode, professional_mode]:
factories.CourseEntitlementFactory(course=course, mode=mode)
applicable_seat_types = SeatType.objects.filter(name__in=['verified', 'professional'])
program_type = factories.ProgramTypeFactory(applicable_seat_types=applicable_seat_types)
program = factories.ProgramFactory(type=program_type, courses=[course])
assert set(course.entitlements.filter(mode__in=applicable_seat_types)) == set(program.entitlements)
def test_languages(self):
expected_languages = set([course_run.language for course_run in self.course_runs])
actual_languages = self.program.languages
......@@ -918,6 +933,30 @@ class ProgramTests(TestCase):
expected_price_ranges = [{'currency': 'USD', 'min': Decimal(100), 'max': Decimal(300), 'total': Decimal(600)}]
self.assertEqual(self.program.price_ranges, expected_price_ranges)
@ddt.data(True, False)
def test_price_ranges_with_entitlements(self, create_seats):
""" Verifies the price_range property of a program with course entitlement products """
currency = Currency.objects.get(code='USD')
test_price = 100
verified_mode = SeatType.objects.get(name='verified')
for course_run in self.course_runs:
factories.CourseEntitlementFactory(
currency=currency, course=course_run.course, price=test_price, mode=verified_mode
)
if create_seats:
factories.SeatFactory(type='verified', currency=currency, price=test_price, course_run=course_run)
course_run.course.canonical_course_run = course_run
course_run.course.save()
test_price += 100
applicable_seat_types = SeatType.objects.filter(name__in=['verified'])
program_type = factories.ProgramTypeFactory(applicable_seat_types=applicable_seat_types)
self.program.type = program_type
expected_price_ranges = [{'currency': 'USD', 'min': Decimal(100), 'max': Decimal(300), 'total': Decimal(600)}]
self.assertEqual(self.program.price_ranges, expected_price_ranges)
def create_program_with_multiple_course_runs(self, set_all_dates=True):
currency = Currency.objects.get(code='USD')
single_course_course_runs = factories.CourseRunFactory.create_batch(3)
......
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