Commit 99e8596f by Julia Hansbrough

Response to CR

parent 893acc57
...@@ -581,9 +581,15 @@ class CourseEnrollment(models.Model): ...@@ -581,9 +581,15 @@ class CourseEnrollment(models.Model):
) )
@classmethod @classmethod
def enrollments_in(cls, course_id): def enrollments_in(cls, course_id, mode=None):
"""Return a queryset of CourseEnrollment for every active enrollment in the course.""" """
return cls.objects.filter(course_id=course_id, is_active=True,) Return a queryset of CourseEnrollment for every active enrollment in the course course_id.
Returns only CourseEnrollments with the given mode, if a mode is supplied by the caller.
"""
if mode is None:
return cls.objects.filter(course_id=course_id, is_active=True,)
else:
return cls.objects.filter(course_id=course_id, is_active=True, mode=mode)
def activate(self): def activate(self):
"""Makes this `CourseEnrollment` record active. Saves immediately.""" """Makes this `CourseEnrollment` record active. Saves immediately."""
......
...@@ -205,13 +205,13 @@ class OrderItem(models.Model): ...@@ -205,13 +205,13 @@ class OrderItem(models.Model):
# this is denormalized, but convenient for SQL queries for reports, etc. user should always be = order.user # this is denormalized, but convenient for SQL queries for reports, etc. user should always be = order.user
user = models.ForeignKey(User, db_index=True) user = models.ForeignKey(User, db_index=True)
# this is denormalized, but convenient for SQL queries for reports, etc. status should always be = order.status # this is denormalized, but convenient for SQL queries for reports, etc. status should always be = order.status
status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES) status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES, db_index=True)
qty = models.IntegerField(default=1) qty = models.IntegerField(default=1)
unit_cost = models.DecimalField(default=0.0, decimal_places=2, max_digits=30) unit_cost = models.DecimalField(default=0.0, decimal_places=2, max_digits=30)
line_desc = models.CharField(default="Misc. Item", max_length=1024) line_desc = models.CharField(default="Misc. Item", max_length=1024)
currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes
fulfilled_time = models.DateTimeField(null=True) fulfilled_time = models.DateTimeField(null=True, db_index=True)
refund_requested_time = models.DateTimeField(null=True) refund_requested_time = models.DateTimeField(null=True, db_index=True)
service_fee = models.DecimalField(default=0.0, decimal_places=2, max_digits=30) service_fee = models.DecimalField(default=0.0, decimal_places=2, max_digits=30)
# general purpose field, not user-visible. Used for reporting # general purpose field, not user-visible. Used for reporting
report_comments = models.TextField(default="") report_comments = models.TextField(default="")
...@@ -568,3 +568,8 @@ class CertificateItem(OrderItem): ...@@ -568,3 +568,8 @@ class CertificateItem(OrderItem):
"Please include your order number in your e-mail. " "Please include your order number in your e-mail. "
"Please do NOT include your credit card information.").format( "Please do NOT include your credit card information.").format(
billing_email=settings.PAYMENT_SUPPORT_EMAIL) billing_email=settings.PAYMENT_SUPPORT_EMAIL)
@classmethod
def verified_certificates_in(cls, course_id, status):
"""Return a queryset of CertificateItem for every verified enrollment in course_id with the given status."""
return CertificateItem.objects.filter(course_id=course_id, mode='verified', status=status)
...@@ -9,31 +9,25 @@ from course_modes.models import CourseMode ...@@ -9,31 +9,25 @@ from course_modes.models import CourseMode
from decimal import Decimal from decimal import Decimal
class Report(models.Model): class Report(Object):
""" """
Base class for making CSV reports related to revenue, enrollments, etc Base class for making CSV reports related to revenue, enrollments, etc
To make a different type of report, write a new subclass that implements To make a different type of report, write a new subclass that implements
the methods get_report_data, csv_report_header_row, and csv_report_row. the methods report_row_generator and csv_report_header_row.
""" """
def get_report_data(self, start_date, end_date, start_letter=None, end_letter=None): def report_row_generator(self, start_date, end_date, start_letter=None, end_letter=None):
""" """
Performs database queries necessary for the report. May return either a query result Performs database queries necessary for the report. Returns an generator of
or a list of lists, depending on the particular type of report--see Report subclasses lists, in which each list is a separate row of the report.
for sample implementations.
""" """
raise NotImplementedError raise NotImplementedError
def csv_report_header_row(self): def csv_report_header_row(self):
""" """
Returns the appropriate header based on the report type. Returns the appropriate header based on the report type, in the form of a
""" list of strings.
raise NotImplementedError
def csv_report_row(self, item):
"""
Given the results of get_report_data, this function generates a single row of a csv.
""" """
raise NotImplementedError raise NotImplementedError
...@@ -42,23 +36,36 @@ class Report(models.Model): ...@@ -42,23 +36,36 @@ class Report(models.Model):
Given the string report_type, a file object to write to, and start/end date bounds, Given the string report_type, a file object to write to, and start/end date bounds,
generates a CSV report of the appropriate type. generates a CSV report of the appropriate type.
""" """
items = self.get_report_data(start_date, end_date, start_letter, end_letter) items = self.report_row_generator(start_date, end_date, start_letter, end_letter)
writer = unicodecsv.writer(filelike, encoding="utf-8") writer = unicodecsv.writer(filelike, encoding="utf-8")
writer.writerow(self.csv_report_header_row()) writer.writerow(self.csv_report_header_row())
for item in items: for item in items:
writer.writerow(self.csv_report_row(item)) writer.writerow(item)
class RefundReport(Report): class RefundReport(Report):
""" """
Subclass of Report, used to generate Refund Reports for finance purposes. Subclass of Report, used to generate Refund Reports for finance purposes.
For each refund between a given start_date and end_date, we find the relevant
order number, customer name, date of transaction, date of refund, and any service
fees.
""" """
def get_report_data(self, start_date, end_date, start_letter=None, end_letter=None): def report_row_generator(self, start_date, end_date, start_letter=None, end_letter=None):
return CertificateItem.objects.filter( query = CertificateItem.objects.select_related('user__profile').filter(
status="refunded", status="refunded",
refund_requested_time__gte=start_date, refund_requested_time__gte=start_date,
refund_requested_time__lt=end_date, refund_requested_time__lt=end_date,
) ).order_by('refund_requested_time')
for item in query:
yield [
item.order_id,
item.user.profile.name,
item.fulfilled_time,
item.refund_requested_time,
item.line_cost,
item.service_fee,
]
def csv_report_header_row(self): def csv_report_header_row(self):
return [ return [
...@@ -70,28 +77,35 @@ class RefundReport(Report): ...@@ -70,28 +77,35 @@ class RefundReport(Report):
"Service Fees (if any)", "Service Fees (if any)",
] ]
def csv_report_row(self, item):
return [
item.order_id,
item.user.get_full_name(),
item.fulfilled_time,
item.refund_requested_time, # TODO Change this torefund_fulfilled once we start recording that value
item.line_cost,
item.service_fee,
]
class ItemizedPurchaseReport(Report): class ItemizedPurchaseReport(Report):
""" """
Subclass of Report, used to generate itemized purchase reports. Subclass of Report, used to generate itemized purchase reports.
For all purchases (verified certificates, paid course registrations, etc) between
a given start_date and end_date, we find that purchase's time, order ID, status,
quantity, unit cost, total cost, currency, description, and related comments.
""" """
def get_report_data(self, start_date, end_date, start_letter=None, end_letter=None): def report_row_generator(self, start_date, end_date, start_letter=None, end_letter=None):
return OrderItem.objects.filter( query = OrderItem.objects.filter(
status="purchased", status="purchased",
fulfilled_time__gte=start_date, fulfilled_time__gte=start_date,
fulfilled_time__lt=end_date, fulfilled_time__lt=end_date,
).order_by("fulfilled_time") ).order_by("fulfilled_time")
for item in query:
yield [
item.fulfilled_time,
item.order_id, # pylint: disable=no-member
item.status,
item.qty,
item.unit_cost,
item.line_cost,
item.currency,
item.line_desc,
item.report_comments,
]
def csv_report_header_row(self): def csv_report_header_row(self):
return [ return [
"Purchase Time", "Purchase Time",
...@@ -105,47 +119,50 @@ class ItemizedPurchaseReport(Report): ...@@ -105,47 +119,50 @@ class ItemizedPurchaseReport(Report):
"Comments" "Comments"
] ]
def csv_report_row(self, item):
return [
item.fulfilled_time,
item.order_id, # pylint: disable=no-member
item.status,
item.qty,
item.unit_cost,
item.line_cost,
item.currency,
item.line_desc,
item.report_comments,
]
class CertificateStatusReport(Report): class CertificateStatusReport(Report):
""" """
Subclass of Report, used to generate Certificate Status Reports for ed services. Subclass of Report, used to generate Certificate Status Reports for Ed Services.
For each course in each university whose name is within the range start_letter and end_letter,
inclusive, (i.e., the letter range H-J includes both Ithaca College and Harvard University), we
calculate the total enrollment, audit enrollment, honor enrollment, verified enrollment, total
gross revenue, gross revenue over the minimum, and total dollars refunded.
""" """
def get_report_data(self, start_date, end_date, start_letter=None, end_letter=None): def report_row_generator(self, start_date, end_date, start_letter=None, end_letter=None):
results = [] results = []
for course_id in settings.COURSE_LISTINGS['default']: for course_id in settings.COURSE_LISTINGS['default']:
# If the first letter of the university is between start_letter and end_letter, then we include
# it in the report. These comparisons are unicode-safe.
if (start_letter.lower() <= course_id.lower()) and (end_letter.lower() >= course_id.lower()) and (get_course_by_id(course_id) is not None): if (start_letter.lower() <= course_id.lower()) and (end_letter.lower() >= course_id.lower()) and (get_course_by_id(course_id) is not None):
cur_course = get_course_by_id(course_id) cur_course = get_course_by_id(course_id)
university = cur_course.org university = cur_course.org
course = cur_course.number + " " + cur_course.display_name # TODO add term (i.e. Fall 2013)? course = cur_course.number + " " + cur_course.display_name_with_default # TODO add term (i.e. Fall 2013)?
enrollments = CourseEnrollment.enrollments_in(course_id) enrollments = CourseEnrollment.enrollments_in(course_id)
total_enrolled = enrollments.count() total_enrolled = enrollments.count()
audit_enrolled = enrollments.filter(mode="audit").count() audit_enrolled = CourseEnrollment.enrollments_in(course_id, "audit").count()
honor_enrolled = enrollments.filter(mode="honor").count() honor_enrolled = CourseEnrollment.enrollments_in(course_id, "honor").count()
# Since every verified enrollment has 1 and only 1 cert item, let's just query those # Since every verified enrollment has 1 and only 1 cert item, let's just query those
verified_enrollments = CertificateItem.objects.filter(course_id=course_id, mode="verified", status="purchased") verified_enrollments = CertificateItem.verified_certificates_in(course_id, 'purchased')
verified_enrolled = verified_enrollments.count() if verified_enrollments is None:
gross_rev_temp = CertificateItem.objects.filter(course_id=course_id, mode="verified", status="purchased").aggregate(Sum('unit_cost')) verified_enrolled = 0
gross_rev = gross_rev_temp['unit_cost__sum'] gross_rev = Decimal(0.00)
gross_rev_over_min = gross_rev - (CourseMode.objects.get(course_id=course_id, mode_slug="verified").min_price * verified_enrolled) gross_rev_over_min = Decimal(0.00)
refunded_enrollments = CertificateItem.objects.filter(course_id='course_id', mode="verified", status="refunded") else:
number_of_refunds = refunded_enrollments.count() verified_enrolled = verified_enrollments.count()
dollars_refunded_temp = refunded_enrollments.aggregate(Sum('unit_cost')) gross_rev_temp = verified_enrollments.aggregate(Sum('unit_cost'))
if dollars_refunded_temp['unit_cost__sum'] is None: gross_rev = gross_rev_temp['unit_cost__sum']
gross_rev_over_min = gross_rev - (CourseMode.objects.get(course_id=course_id, mode_slug="verified").min_price * verified_enrolled)
# should I be worried about is_active here?
refunded_enrollments = CertificateItem.verified_certificates_in(course_id, 'refunded')
if refunded_enrollments is None:
number_of_refunds = 0
dollars_refunded = Decimal(0.00) dollars_refunded = Decimal(0.00)
else: else:
number_of_refunds = refunded_enrollments.count()
dollars_refunded_temp = refunded_enrollments.aggregate(Sum('unit_cost'))
dollars_refunded = dollars_refunded_temp['unit_cost__sum'] dollars_refunded = dollars_refunded_temp['unit_cost__sum']
result = [ result = [
...@@ -162,7 +179,8 @@ class CertificateStatusReport(Report): ...@@ -162,7 +179,8 @@ class CertificateStatusReport(Report):
] ]
results.append(result) results.append(result)
return results for item in results:
yield item
def csv_report_header_row(self): def csv_report_header_row(self):
return [ return [
...@@ -178,41 +196,43 @@ class CertificateStatusReport(Report): ...@@ -178,41 +196,43 @@ class CertificateStatusReport(Report):
"Dollars Refunded", "Dollars Refunded",
] ]
def csv_report_row(self, item):
return item
class UniversityRevenueShareReport(Report): class UniversityRevenueShareReport(Report):
""" """
Subclass of Report, used to generate University Revenue Share Reports for finance purposes. Subclass of Report, used to generate University Revenue Share Reports for finance purposes.
For each course in each university whose name is within the range start_letter and end_letter,
inclusive, (i.e., the letter range H-J includes both Ithaca College and Harvard University), we calculate
the total revenue generated by that particular course. This includes the number of transactions,
total payments collected, service fees, number of refunds, and total amount of refunds.
""" """
def get_report_data(self, start_date, end_date, start_letter=None, end_letter=None): def report_row_generator(self, start_date, end_date, start_letter=None, end_letter=None):
results = [] results = []
for course_id in settings.COURSE_LISTINGS['default']: for course_id in settings.COURSE_LISTINGS['default']:
# If the first letter of the university is between start_letter and end_letter, then we include
# it in the report. These comparisons are unicode-safe.
if (start_letter.lower() <= course_id.lower()) and (end_letter.lower() >= course_id.lower()): if (start_letter.lower() <= course_id.lower()) and (end_letter.lower() >= course_id.lower()):
try: try:
cur_course = get_course_by_id(course_id) cur_course = get_course_by_id(course_id)
except: except:
break break
university = cur_course.org university = cur_course.org
course = cur_course.number + " " + cur_course.display_name course = cur_course.number + " " + cur_course.display_name_with_default
num_transactions = 0 # TODO clarify with billing what transactions are included in this (purchases? refunds? etc) num_transactions = 0 # TODO clarify with billing what transactions are included in this (purchases? refunds? etc)
all_paid_certs = CertificateItem.objects.filter(course_id=course_id, status="purchased") all_paid_certs = CertificateItem.verified_certificates_in(course_id, "purchased")
try:
total_payments_collected_temp = all_paid_certs.aggregate(Sum('unit_cost')) total_payments_collected_temp = all_paid_certs.aggregate(Sum('unit_cost'))
if total_payments_collected_temp['unit_cost__sum'] is None:
total_payments_collected = Decimal(0.00)
else:
total_payments_collected = total_payments_collected_temp['unit_cost__sum'] total_payments_collected = total_payments_collected_temp['unit_cost__sum']
except:
total_service_fees_temp = all_paid_certs.aggregate(Sum('service_fee')) total_payments_collected = Decimal(0.00)
if total_service_fees_temp['service_fee__sum'] is None: try:
service_fees = Decimal(0.00) total_service_fees_temp = all_paid_certs.aggregate(Sum('service_fee'))
else:
service_fees = total_service_fees_temp['service_fee__sum'] service_fees = total_service_fees_temp['service_fee__sum']
except:
service_fees = Decimal(0.00)
refunded_enrollments = CertificateItem.objects.filter(course_id=course_id, status="refunded") refunded_enrollments = CertificateItem.verified_certificates_in(course_id, "refunded")
num_refunds = refunded_enrollments.count() num_refunds = refunded_enrollments.count()
amount_refunds_temp = refunded_enrollments.aggregate(Sum('unit_cost')) amount_refunds_temp = refunded_enrollments.aggregate(Sum('unit_cost'))
...@@ -232,7 +252,8 @@ class UniversityRevenueShareReport(Report): ...@@ -232,7 +252,8 @@ class UniversityRevenueShareReport(Report):
] ]
results.append(result) results.append(result)
return results for item in results:
yield item
def csv_report_header_row(self): def csv_report_header_row(self):
return [ return [
...@@ -244,6 +265,3 @@ class UniversityRevenueShareReport(Report): ...@@ -244,6 +265,3 @@ class UniversityRevenueShareReport(Report):
"Number of Successful Refunds", "Number of Successful Refunds",
"Total Amount of Refunds", "Total Amount of Refunds",
] ]
def csv_report_row(self, item):
return item
...@@ -356,32 +356,47 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase): ...@@ -356,32 +356,47 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
self.cart.purchase() self.cart.purchase()
self.now = datetime.datetime.now(pytz.UTC) self.now = datetime.datetime.now(pytz.UTC)
# We can't modify the values returned by report_row_generator directly, since it's a generator, but
# we need the times on CORRECT_CSV and the generated report to match. So, we extract the times from
# the report_row_generator and place them in CORRECT_CSV.
self.time_str = {}
report = initialize_report("itemized_purchase_report")
purchases = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
num_of_item = 0
for item in purchases:
num_of_item += 1
self.time_str[num_of_item] = item[0]
self.CORRECT_CSV = dedent("""
Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments
{time_str1},1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85
{time_str2},1,purchased,1,40,40,usd,"Certificate of Achievement, verified cert for course Robot Super Course",
""".format(time_str1=str(self.time_str[1]), time_str2=str(self.time_str[2])))
def test_purchased_items_btw_dates(self): def test_purchased_items_btw_dates(self):
report = initialize_report("itemized_purchase_report") report = initialize_report("itemized_purchase_report")
purchases = report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) purchases = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
self.assertEqual(len(purchases), 2)
self.assertIn(self.reg.orderitem_ptr, purchases)
self.assertIn(self.cert_item.orderitem_ptr, purchases)
no_purchases = report.get_report_data(self.now + self.FIVE_MINS, self.now + self.FIVE_MINS + self.FIVE_MINS)
self.assertFalse(no_purchases)
test_time = datetime.datetime.now(pytz.UTC) # since there's not many purchases, just run through the generator to make sure we've got the right number
num_purchases = 0
for item in purchases:
num_purchases += 1
self.assertEqual(num_purchases, 2)
#self.assertIn(self.reg.orderitem_ptr, purchases)
#self.assertIn(self.cert_item.orderitem_ptr, purchases)
CORRECT_CSV = dedent(""" no_purchases = report.report_row_generator(self.now + self.FIVE_MINS, self.now + self.FIVE_MINS + self.FIVE_MINS)
Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments
{time_str},1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85 num_purchases = 0
{time_str},1,purchased,1,40,40,usd,"Certificate of Achievement, verified cert for course Robot Super Course", for item in no_purchases:
""".format(time_str=str(test_time))) num_purchases +=1
self.assertEqual(num_purchases, 0)
def test_purchased_csv(self): def test_purchased_csv(self):
""" """
Tests that a generated purchase report CSV is as we expect Tests that a generated purchase report CSV is as we expect
""" """
report = initialize_report("itemized_purchase_report") report = initialize_report("itemized_purchase_report")
for item in report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS):
item.fulfilled_time = self.test_time
item.save()
csv_file = StringIO.StringIO() csv_file = StringIO.StringIO()
report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
csv = csv_file.getvalue() csv = csv_file.getvalue()
......
...@@ -29,44 +29,36 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -29,44 +29,36 @@ class ReportTypeTests(ModuleStoreTestCase):
def setUp(self): def setUp(self):
# Need to make a *lot* of users for this one # Need to make a *lot* of users for this one
self.user1 = UserFactory.create() self.user1 = UserFactory.create()
self.user1.first_name = "John" self.user1.profile.name = "John Doe"
self.user1.last_name = "Doe" self.user1.profile.save()
self.user1.save()
self.user2 = UserFactory.create() self.user2 = UserFactory.create()
self.user2.first_name = "Jane" self.user2.profile.name = "Jane Deer"
self.user2.last_name = "Deer" self.user2.profile.save()
self.user2.save()
self.user3 = UserFactory.create() self.user3 = UserFactory.create()
self.user3.first_name = "Joe" self.user3.profile.name = "Joe Miller"
self.user3.last_name = "Miller" self.user3.profile.save()
self.user3.save()
self.user4 = UserFactory.create() self.user4 = UserFactory.create()
self.user4.first_name = "Simon" self.user4.profile.name = "Simon Blackquill"
self.user4.last_name = "Blackquill" self.user4.profile.save()
self.user4.save()
self.user5 = UserFactory.create() self.user5 = UserFactory.create()
self.user5.first_name = "Super" self.user5.profile.name = "Super Mario"
self.user5.last_name = "Mario" self.user5.profile.save()
self.user5.save()
self.user6 = UserFactory.create() self.user6 = UserFactory.create()
self.user6.first_name = "Princess" self.user6.profile.name = "Princess Peach"
self.user6.last_name = "Peach" self.user6.profile.save()
self.user6.save()
self.user7 = UserFactory.create() self.user7 = UserFactory.create()
self.user7.first_name = "King" self.user7.profile.name = "King Bowser"
self.user7.last_name = "Bowser" self.user7.profile.save()
self.user7.save()
self.user8 = UserFactory.create() self.user8 = UserFactory.create()
self.user8.first_name = "Susan" self.user8.profile.name = "Susan Smith"
self.user8.last_name = "Smith" self.user8.profile.save()
self.user8.save()
# Two are verified, three are audit, one honor # Two are verified, three are audit, one honor
...@@ -116,16 +108,29 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -116,16 +108,29 @@ class ReportTypeTests(ModuleStoreTestCase):
self.cart.purchase(self.user8, self.course_id) self.cart.purchase(self.user8, self.course_id)
CourseEnrollment.unenroll(self.user8, self.course_id) CourseEnrollment.unenroll(self.user8, self.course_id)
self.test_time = datetime.datetime.now(pytz.UTC) # We can't modify the values returned by report_row_generator directly, since it's a generator, but
# we need the times on CORRECT_CSV and the generated report to match. So, we extract the times from
# the report_row_generator and place them in CORRECT_CSV.
self.time_str = {}
report = initialize_report("refund_report")
refunds = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
time_index = 0
for item in refunds:
self.time_str[time_index] = item[2]
time_index += 1
self.time_str[time_index] = item[3]
time_index += 1
self.CORRECT_REFUND_REPORT_CSV = dedent(""" self.CORRECT_REFUND_REPORT_CSV = dedent("""
Order Number,Customer Name,Date of Original Transaction,Date of Refund,Amount of Refund,Service Fees (if any) Order Number,Customer Name,Date of Original Transaction,Date of Refund,Amount of Refund,Service Fees (if any)
3,King Bowser,{time_str},{time_str},40,0 3,King Bowser,{time_str0},{time_str1},40,0
4,Susan Smith,{time_str},{time_str},40,0 4,Susan Smith,{time_str2},{time_str3},40,0
""".format(time_str=str(self.test_time))) """.format(time_str0=str(self.time_str[0]), time_str1=str(self.time_str[1]), time_str2=str(self.time_str[2]), time_str3=str(self.time_str[3])))
self.test_time = datetime.datetime.now(pytz.UTC)
self.CORRECT_CERT_STATUS_CSV = dedent(""" self.CORRECT_CERT_STATUS_CSV = dedent("""
University,Course,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Refunds,Dollars Refunded University,Course,Total Enrolled,Audit Enrollment,Honor Code Enrollment,Verified Enrollment,Gross Revenue,Gross Revenue over the Minimum,Number of Refunds,Dollars Refunded
MITx,999 Robot Super Course,6,3,1,2,80.00,0.00,0,0 MITx,999 Robot Super Course,6,3,1,2,80.00,0.00,2,80.00
""".format(time_str=str(self.test_time))) """.format(time_str=str(self.test_time)))
self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent(""" self.CORRECT_UNI_REVENUE_SHARE_CSV = dedent("""
...@@ -133,10 +138,16 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -133,10 +138,16 @@ class ReportTypeTests(ModuleStoreTestCase):
MITx,999 Robot Super Course,0,80.00,0.00,2,80.00 MITx,999 Robot Super Course,0,80.00,0.00,2,80.00
""".format(time_str=str(self.test_time))) """.format(time_str=str(self.test_time)))
def test_refund_report_get_report_data(self): def test_refund_report_report_row_generator(self):
report = initialize_report("refund_report") report = initialize_report("refund_report")
refunded_certs = report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) refunded_certs = report.report_row_generator(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
self.assertEqual(len(refunded_certs), 2)
# check that we have the right number
num_certs = 0
for cert in refunded_certs:
num_certs += 1
self.assertEqual(num_certs, 2)
self.assertTrue(CertificateItem.objects.get(user=self.user7, course_id=self.course_id)) self.assertTrue(CertificateItem.objects.get(user=self.user7, course_id=self.course_id))
self.assertTrue(CertificateItem.objects.get(user=self.user8, course_id=self.course_id)) self.assertTrue(CertificateItem.objects.get(user=self.user8, course_id=self.course_id))
...@@ -145,11 +156,6 @@ class ReportTypeTests(ModuleStoreTestCase): ...@@ -145,11 +156,6 @@ class ReportTypeTests(ModuleStoreTestCase):
Tests that a generated purchase report CSV is as we expect Tests that a generated purchase report CSV is as we expect
""" """
report = initialize_report("refund_report") report = initialize_report("refund_report")
for item in report.get_report_data(self.now - self.FIVE_MINS, self.now + self.FIVE_MINS):
item.fulfilled_time = self.test_time
item.refund_requested_time = self.test_time # hm do we want to make these different
item.save()
csv_file = StringIO.StringIO() csv_file = StringIO.StringIO()
report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS) report.make_report(csv_file, self.now - self.FIVE_MINS, self.now + self.FIVE_MINS)
csv = csv_file.getvalue() csv = csv_file.getvalue()
......
...@@ -213,7 +213,7 @@ def csv_report(request): ...@@ -213,7 +213,7 @@ def csv_report(request):
return _render_report_form(start_str, end_str, start_letter, end_letter, report_type, date_fmt_error=True) return _render_report_form(start_str, end_str, start_letter, end_letter, report_type, date_fmt_error=True)
report = initialize_report(report_type) report = initialize_report(report_type)
items = report.get_report_data(start_date, end_date, start_letter, end_letter) items = report.report_row_generator(start_date, end_date, start_letter, end_letter)
# TODO add this back later as a query-est function or something # TODO add this back later as a query-est function or something
try: try:
......
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