Commit 0d37014e by Simon Chen

Add program total price to the price_ranges array ECOM-7023

parent 759512a8
......@@ -810,18 +810,75 @@ class Program(TimeStampedModel):
def seat_types(self):
return set(seat.type for seat in self.seats)
def _get_total_price_by_currency(self):
"""
This helper function returns the total program price indexed by the currency
"""
currencies_with_total = defaultdict()
course_map = defaultdict(list)
for seat in self.seats:
course_uuid = seat.course_run.course.uuid
# Identify the most relevant course_run seat for a course.
# 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
course_map[course_uuid] = [seat]
else:
add_seat = False
seats_to_remove = []
for selected_seat in selected_seats:
if seat.currency != selected_seat.currency:
# If the candidate seat has a different currency than the one in the array,
# always add to the array
add_seat = True
elif ((seat.course_run.end is None or
seat.course_run.end >= datetime.datetime.now(pytz.UTC)) and
(seat.course_run.enrollment_start is None or
seat.course_run.enrollment_start > selected_seat.course_run.enrollment_start and
seat.course_run.enrollment_start < datetime.datetime.now(pytz.UTC))):
# If the seat has same currency, the course has not ended,
# and the course is enrollable, then choose the new seat associated with the course instead,
# and mark the original seat in the array to be removed
logger.info(
"Use course_run {} instead of course_run {} for total program price calculation".format(
seat.course_run.key,
selected_seat.course_run.key
)
)
add_seat = True
seats_to_remove.append(selected_seat)
if add_seat:
course_map[course_uuid].append(seat)
for seat in seats_to_remove:
# Now remove the seats that should not be counted for calculation for program total
course_map[course_uuid].remove(seat)
# 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
return currencies_with_total
@property
def price_ranges(self):
currencies = defaultdict(list)
for seat in self.seats:
currencies[seat.currency].append(seat.price)
total_by_currency = self._get_total_price_by_currency()
price_ranges = []
for currency, prices in currencies.items():
price_ranges.append({
'currency': currency.code,
'min': min(prices),
'max': max(prices),
'total': total_by_currency.get(currency, 0)
})
return price_ranges
......
import datetime
import itertools
from decimal import Decimal
......@@ -485,9 +486,63 @@ class ProgramTests(MarketingSitePublisherTestMixin):
self.assertIsNone(self.program.start)
def test_price_ranges(self):
""" Verify the price_ranges property of the program is returning expected price values """
program = self.create_program_with_seats()
expected_price_ranges = [{'currency': 'USD', 'min': Decimal(100), 'max': Decimal(600)}]
expected_price_ranges = [{'currency': 'USD', 'min': Decimal(100), 'max': Decimal(600), 'total': Decimal(600)}]
self.assertEqual(program.price_ranges, expected_price_ranges)
def test_price_ranges_multiple_course(self):
""" Verifies the price_range property of a program with multiple courses """
currency = Currency.objects.get(code='USD')
test_price = 100
for course_run in self.course_runs:
factories.SeatFactory(type='audit', currency=currency, course_run=course_run, price=0)
factories.SeatFactory(type='verified', currency=currency, course_run=course_run, price=test_price)
test_price += 100
applicable_seat_types = SeatType.objects.filter(slug__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):
currency = Currency.objects.get(code='USD')
single_course_course_runs = factories.CourseRunFactory.create_batch(3)
course = factories.CourseFactory()
course_runs_same_course = factories.CourseRunFactory.create_batch(2, course=course)
for course_run in single_course_course_runs:
factories.SeatFactory(type='audit', currency=currency, course_run=course_run, price=0)
factories.SeatFactory(type='verified', currency=currency, course_run=course_run, price=10)
day_diff = 1
for course_run in course_runs_same_course:
course_run.enrollment_start = datetime.datetime.now() - datetime.timedelta(days=day_diff)
course_run.end = datetime.datetime.now() + datetime.timedelta(weeks=day_diff)
course_run.save()
factories.SeatFactory(type='audit', currency=currency, course_run=course_run, price=0)
factories.SeatFactory(type='verified', currency=currency, course_run=course_run, price=(day_diff * 100))
day_diff += 1
applicable_seat_types = SeatType.objects.filter(slug__in=['verified'])
program_type = factories.ProgramTypeFactory(applicable_seat_types=applicable_seat_types)
program_courses = [course_run.course for course_run in single_course_course_runs]
program_courses.append(course)
return factories.ProgramFactory(type=program_type, courses=program_courses)
def test_price_ranges_with_multiple_course_runs(self):
"""
Verifies the price_range property of a program with multiple courses,
and a course with multiple runs
"""
program = self.create_program_with_multiple_course_runs()
expected_price_ranges = [{'currency': 'USD', 'min': Decimal(10), 'max': Decimal(200), 'total': Decimal(130)}]
self.assertEqual(program.price_ranges, expected_price_ranges)
def test_staff(self):
......
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