diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 2a45f2d838..44a64cc5a6 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -299,8 +299,14 @@ class DashboardTest(ModuleStoreTestCase): recipient_name='Testw_1', recipient_email='test2@test.com', internal_reference="A", course_id=self.course.id, is_valid=False ) + invoice_item = shoppingcart.models.CourseRegistrationCodeInvoiceItem.objects.create( + invoice=sale_invoice_1, + qty=1, + unit_price=1234.32, + course_id=self.course.id + ) course_reg_code = shoppingcart.models.CourseRegistrationCode( - code="abcde", course_id=self.course.id, created_by=self.user, invoice=sale_invoice_1, mode_slug='honor' + code="abcde", course_id=self.course.id, created_by=self.user, invoice=sale_invoice_1, invoice_item=invoice_item, mode_slug='honor' ) course_reg_code.save() diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 774cf23168..87efc86224 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -465,8 +465,8 @@ def is_course_blocked(request, redeemed_registration_codes, course_key): # registration codes may be generated via Bulk Purchase Scenario # we have to check only for the invoice generated registration codes # that their invoice is valid or not - if redeemed_registration.invoice: - if not getattr(redeemed_registration.invoice, 'is_valid'): + if redeemed_registration.invoice_item: + if not getattr(redeemed_registration.invoice_item.invoice, 'is_valid'): blocked = True # disabling email notifications for unpaid registration courses Optout.objects.get_or_create(user=request.user, course_id=course_key) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index e2cf283537..3e095c0d39 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -39,7 +39,7 @@ from django_comment_common.utils import seed_permissions_roles from microsite_configuration import microsite from shoppingcart.models import ( RegistrationCodeRedemption, Order, CouponRedemption, - PaidCourseRegistration, Coupon, Invoice, CourseRegistrationCode + PaidCourseRegistration, Coupon, Invoice, CourseRegistrationCode, CourseRegistrationCodeInvoiceItem ) from shoppingcart.pdf import PDFInvoice from student.models import ( @@ -1713,6 +1713,12 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa recipient_name='Testw', recipient_email='test1@test.com', customer_reference_number='2Fwe23S', internal_reference="A", course_id=self.course.id, is_valid=True ) + self.invoice_item = CourseRegistrationCodeInvoiceItem.objects.create( + invoice=self.sale_invoice_1, + qty=1, + unit_price=1234.32, + course_id=self.course.id + ) self.students = [UserFactory() for _ in xrange(6)] for student in self.students: @@ -1724,8 +1730,12 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa """ for i in range(2): course_registration_code = CourseRegistrationCode( - code='sale_invoice{}'.format(i), course_id=self.course.id.to_deprecated_string(), - created_by=self.instructor, invoice=self.sale_invoice_1, mode_slug='honor' + code='sale_invoice{}'.format(i), + course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, + invoice=self.sale_invoice_1, + invoice_item=self.invoice_item, + mode_slug='honor' ) course_registration_code.save() @@ -1745,7 +1755,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa data['event_type'] = "re_validate" self.assert_request_status_code(200, url, method="POST", data=data) - # Now re_validate the same actove invoice number and expect an Bad request + # Now re_validate the same active invoice number and expect an Bad request response = self.assert_request_status_code(400, url, method="POST", data=data) self.assertIn("This invoice is already active.", response.content) @@ -1844,12 +1854,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa """ for i in range(2): course_registration_code = CourseRegistrationCode( - code='sale_invoice{}'.format(i), course_id=self.course.id.to_deprecated_string(), - created_by=self.instructor, invoice=self.sale_invoice_1, mode_slug='honor' + code='sale_invoice{}'.format(i), + course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, + invoice=self.sale_invoice_1, + invoice_item=self.invoice_item, + mode_slug='honor' ) course_registration_code.save() - url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) + url = reverse( + 'get_sale_records', + kwargs={'course_id': self.course.id.to_deprecated_string()} + ) response = self.client.get(url + '/csv', {}) self.assertEqual(response['Content-Type'], 'text/csv') @@ -1859,8 +1876,12 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa """ for i in range(5): course_registration_code = CourseRegistrationCode( - code='sale_invoice{}'.format(i), course_id=self.course.id.to_deprecated_string(), - created_by=self.instructor, invoice=self.sale_invoice_1, mode_slug='honor' + code='sale_invoice{}'.format(i), + course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, + invoice=self.sale_invoice_1, + invoice_item=self.invoice_item, + mode_slug='honor' ) course_registration_code.save() @@ -1870,7 +1891,13 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa self.assertIn('sale', res_json) for res in res_json['sale']: - self.validate_sale_records_response(res, course_registration_code, self.sale_invoice_1, 0) + self.validate_sale_records_response( + res, + course_registration_code, + self.sale_invoice_1, + 0, + invoice_item=self.invoice_item + ) def test_get_sale_records_features_with_multiple_invoices(self): """ @@ -1878,8 +1905,12 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa """ for i in range(5): course_registration_code = CourseRegistrationCode( - code='qwerty{}'.format(i), course_id=self.course.id.to_deprecated_string(), - created_by=self.instructor, invoice=self.sale_invoice_1, mode_slug='honor' + code='qwerty{}'.format(i), + course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, + invoice=self.sale_invoice_1, + invoice_item=self.invoice_item, + mode_slug='honor' ) course_registration_code.save() @@ -1890,10 +1921,17 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa internal_reference="B", course_id=self.course.id ) + invoice_item_2 = CourseRegistrationCodeInvoiceItem.objects.create( + invoice=sale_invoice_2, + qty=1, + unit_price=1234.32, + course_id=self.course.id + ) + for i in range(5): course_registration_code = CourseRegistrationCode( code='xyzmn{}'.format(i), course_id=self.course.id.to_deprecated_string(), - created_by=self.instructor, invoice=sale_invoice_2, mode_slug='honor' + created_by=self.instructor, invoice=sale_invoice_2, invoice_item=invoice_item_2, mode_slug='honor' ) course_registration_code.save() @@ -1902,10 +1940,22 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa res_json = json.loads(response.content) self.assertIn('sale', res_json) - self.validate_sale_records_response(res_json['sale'][0], course_registration_code, self.sale_invoice_1, 0) - self.validate_sale_records_response(res_json['sale'][1], course_registration_code, sale_invoice_2, 0) + self.validate_sale_records_response( + res_json['sale'][0], + course_registration_code, + self.sale_invoice_1, + 0, + invoice_item=self.invoice_item + ) + self.validate_sale_records_response( + res_json['sale'][1], + course_registration_code, + sale_invoice_2, + 0, + invoice_item=invoice_item_2 + ) - def validate_sale_records_response(self, res, course_registration_code, invoice, used_codes): + def validate_sale_records_response(self, res, course_registration_code, invoice, used_codes, invoice_item): """ validate sale records attribute values with the response object """ @@ -1919,7 +1969,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa self.assertEqual(res['customer_reference_number'], invoice.customer_reference_number) self.assertEqual(res['invoice_number'], invoice.id) self.assertEqual(res['created_by'], course_registration_code.created_by.username) - self.assertEqual(res['course_id'], invoice.course_id.to_deprecated_string()) + self.assertEqual(res['course_id'], invoice_item.course_id.to_deprecated_string()) self.assertEqual(res['total_used_codes'], used_codes) self.assertEqual(res['total_codes'], 5) @@ -3011,7 +3061,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3043,7 +3093,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 5, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 121.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 121.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': 'True' @@ -3065,7 +3115,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 5, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 121.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 121.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': 'True' @@ -3107,7 +3157,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 15, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3129,7 +3179,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 15, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3165,7 +3215,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): coupon.save() data = { 'total_registration_codes': 3, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3189,7 +3239,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 2, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3225,7 +3275,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'sale_price': 122.45, 'company_contact_email': 'Test@company.com', 'recipient_name': 'Test123', + 'unit_price': 122.45, 'company_contact_email': 'Test@company.com', 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3276,7 +3326,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3315,7 +3365,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3342,7 +3392,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ) data = { 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' @@ -3361,7 +3411,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): data = { 'total_registration_codes': 5.5, 'company_name': 'Group Invoice', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': True @@ -3379,6 +3429,26 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): body = response.content.replace('\r', '') self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) + def test_with_invalid_unit_price(self): + """ + Test to generate a response of all the course registration codes + """ + generate_code_url = reverse( + 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()} + ) + + data = { + 'total_registration_codes': 10, 'company_name': 'Group Invoice', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'unit_price': 'invalid', 'recipient_name': 'Test123', + 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', + 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', + 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': True + } + + response = self.client.post(generate_code_url, data, **{'HTTP_HOST': 'localhost'}) + self.assertEqual(response.status_code, 400, response.content) + self.assertIn('Could not parse amount as', response.content) + def test_get_historical_coupon_codes(self): """ Test to download a response of all the active coupon codes diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 8c97f1e899..69fafc6e4c 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -27,6 +27,7 @@ import string # pylint: disable=deprecated-module import random import unicodecsv import urllib +import decimal from student import auth from student.roles import CourseSalesAdminRole from util.file import store_uploaded_file, course_and_time_based_filename_generator, FileValidationException, UniversalNewlineIterator @@ -49,7 +50,14 @@ from django_comment_common.models import ( ) from edxmako.shortcuts import render_to_response, render_to_string from courseware.models import StudentModule -from shoppingcart.models import Coupon, CourseRegistrationCode, RegistrationCodeRedemption, Invoice, CourseMode +from shoppingcart.models import ( + Coupon, + CourseRegistrationCode, + RegistrationCodeRedemption, + Invoice, + CourseMode, + CourseRegistrationCodeInvoiceItem, +) from student.models import CourseEnrollment, unique_id_for_user, anonymous_id_for_user import instructor_task.api from instructor_task.api_helper import AlreadyRunningError @@ -885,9 +893,13 @@ def sale_validation(request, course_id): course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) try: - obj_invoice = Invoice.objects.select_related('is_valid').get(id=invoice_number, course_id=course_id) - except Invoice.DoesNotExist: - return HttpResponseNotFound(_("Invoice number '{0}' does not exist.").format(invoice_number)) + obj_invoice = CourseRegistrationCodeInvoiceItem.objects.select_related('invoice').get( + invoice_id=invoice_number, + course_id=course_id + ) + obj_invoice = obj_invoice.invoice + except CourseRegistrationCodeInvoiceItem.DoesNotExist: # Check for old type invoices + return HttpResponseNotFound(_("Invoice number '{0}' does not exist.".format(invoice_number))) if event_type == "invalidate": return invalidate_invoice(obj_invoice) @@ -1053,7 +1065,7 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument return instructor_analytics.csvs.create_csv_response('Coupons.csv', header, data_rows) -def save_registration_code(user, course_id, mode_slug, invoice=None, order=None): +def save_registration_code(user, course_id, mode_slug, invoice=None, order=None, invoice_item=None): """ recursive function that generate a new code every time and saves in the Course Registration Table if validation check passes @@ -1064,6 +1076,7 @@ def save_registration_code(user, course_id, mode_slug, invoice=None, order=None) mode_slug (str): The Course Mode Slug associated with any enrollment made by these codes. invoice (Invoice): (Optional) The associated invoice for this code. order (Order): (Optional) The associated order for this code. + invoice_item (CourseRegistrationCodeInvoiceItem) : (Optional) The associated CourseRegistrationCodeInvoiceItem Returns: The newly created CourseRegistrationCode. @@ -1074,7 +1087,9 @@ def save_registration_code(user, course_id, mode_slug, invoice=None, order=None) # check if the generated code is in the Coupon Table matching_coupons = Coupon.objects.filter(code=code, is_active=True) if matching_coupons: - return save_registration_code(user, course_id, invoice, order) + return save_registration_code( + user, course_id, mode_slug, invoice=invoice, order=order, invoice_item=invoice_item + ) course_registration = CourseRegistrationCode( code=code, @@ -1082,13 +1097,16 @@ def save_registration_code(user, course_id, mode_slug, invoice=None, order=None) created_by=user, invoice=invoice, order=order, - mode_slug=mode_slug + mode_slug=mode_slug, + invoice_item=invoice_item ) try: course_registration.save() return course_registration except IntegrityError: - return save_registration_code(user, course_id, invoice, order) + return save_registration_code( + user, course_id, mode_slug, invoice=invoice, order=order, invoice_item=invoice_item + ) def registration_codes_csv(file_name, codes_list, csv_type=None): @@ -1130,11 +1148,13 @@ def get_registration_codes(request, course_id): # pylint: disable=unused-argume course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) #filter all the course registration codes - registration_codes = CourseRegistrationCode.objects.filter(course_id=course_id).order_by('invoice__company_name') + registration_codes = CourseRegistrationCode.objects.filter( + course_id=course_id + ).order_by('invoice_item__invoice__company_name') company_name = request.POST['download_company_name'] if company_name: - registration_codes = registration_codes.filter(invoice__company_name=company_name) + registration_codes = registration_codes.filter(invoice_item__invoice__company_name=company_name) csv_type = 'download' return registration_codes_csv("Registration_Codes.csv", registration_codes, csv_type) @@ -1160,7 +1180,21 @@ def generate_registration_codes(request, course_id): company_name = request.POST['company_name'] company_contact_name = request.POST['company_contact_name'] company_contact_email = request.POST['company_contact_email'] - sale_price = request.POST['sale_price'] + unit_price = request.POST['unit_price'] + + try: + unit_price = ( + decimal.Decimal(unit_price) + ).quantize( + decimal.Decimal('.01'), + rounding=decimal.ROUND_DOWN + ) + except decimal.InvalidOperation: + return HttpResponse( + status=400, + content=_(u"Could not parse amount as a decimal") + ) + recipient_name = request.POST['recipient_name'] recipient_email = request.POST['recipient_email'] address_line_1 = request.POST['address_line_1'] @@ -1177,6 +1211,7 @@ def generate_registration_codes(request, course_id): recipient_list.append(request.user.email) invoice_copy = True + sale_price = unit_price * course_code_number UserPreference.set_preference(request.user, INVOICE_KEY, invoice_copy) sale_invoice = Invoice.objects.create( total_amount=sale_price, @@ -1197,6 +1232,13 @@ def generate_registration_codes(request, course_id): customer_reference_number=customer_reference_number ) + invoice_item = CourseRegistrationCodeInvoiceItem.objects.create( + invoice=sale_invoice, + qty=course_code_number, + unit_price=unit_price, + course_id=course_id + ) + course = get_course_by_id(course_id, depth=0) paid_modes = CourseMode.paid_modes_for_course(course_id) @@ -1217,7 +1259,7 @@ def generate_registration_codes(request, course_id): registration_codes = [] for __ in range(course_code_number): # pylint: disable=redefined-outer-name generated_registration_code = save_registration_code( - request.user, course_id, course_mode.slug, sale_invoice, order=None + request.user, course_id, course_mode.slug, invoice=sale_invoice, order=None, invoice_item=invoice_item ) registration_codes.append(generated_registration_code) @@ -1309,13 +1351,17 @@ def active_registration_codes(request, course_id): # pylint: disable=unused-arg course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) # find all the registration codes in this course - registration_codes_list = CourseRegistrationCode.objects.filter(course_id=course_id).order_by('invoice__company_name') + registration_codes_list = CourseRegistrationCode.objects.filter( + course_id=course_id + ).order_by('invoice_item__invoice__company_name') company_name = request.POST['active_company_name'] if company_name: - registration_codes_list = registration_codes_list.filter(invoice__company_name=company_name) + registration_codes_list = registration_codes_list.filter(invoice_item__invoice__company_name=company_name) # find the redeemed registration codes if any exist in the db - code_redemption_set = RegistrationCodeRedemption.objects.select_related('registration_code').filter(registration_code__course_id=course_id) + code_redemption_set = RegistrationCodeRedemption.objects.select_related( + 'registration_code', 'registration_code__invoice_item__invoice' + ).filter(registration_code__course_id=course_id) if code_redemption_set.exists(): redeemed_registration_codes = [code.registration_code.code for code in code_redemption_set] # exclude the redeemed registration codes from the registration codes list and you will get @@ -1346,11 +1392,11 @@ def spent_registration_codes(request, course_id): # pylint: disable=unused-argu # you will get a list of all the spent(Redeemed) Registration Codes spent_codes_list = CourseRegistrationCode.objects.filter( course_id=course_id, code__in=redeemed_registration_codes - ).order_by('invoice__company_name') + ).order_by('invoice_item__invoice__company_name').select_related('invoice_item__invoice') company_name = request.POST['spent_company_name'] if company_name: - spent_codes_list = spent_codes_list.filter(invoice__company_name=company_name) # pylint: disable=maybe-no-member + spent_codes_list = spent_codes_list.filter(invoice_item__invoice__company_name=company_name) # pylint: disable=maybe-no-member csv_type = 'spent' return registration_codes_csv("Spent_Registration_Codes.csv", spent_codes_list, csv_type) diff --git a/lms/djangoapps/instructor_analytics/basic.py b/lms/djangoapps/instructor_analytics/basic.py index 8249acb2c5..6681e3737c 100644 --- a/lms/djangoapps/instructor_analytics/basic.py +++ b/lms/djangoapps/instructor_analytics/basic.py @@ -6,7 +6,7 @@ Serve miscellaneous course and student data import json from shoppingcart.models import ( PaidCourseRegistration, CouponRedemption, Invoice, CourseRegCodeItem, - OrderTypes, RegistrationCodeRedemption, CourseRegistrationCode + OrderTypes, RegistrationCodeRedemption, CourseRegistrationCode, CourseRegistrationCodeInvoiceItem ) from django.db.models import Q from django.conf import settings @@ -110,22 +110,25 @@ def sale_record_features(course_id, features): {'company_name': 'group_C', 'total_codes': '3', total_amount:'total_amount3 in decimal'.} ] """ - sales = Invoice.objects.filter(course_id=course_id) + sales = CourseRegistrationCodeInvoiceItem.objects.select_related('invoice').filter(course_id=course_id) def sale_records_info(sale, features): - """ convert sales records to dictionary """ + """ + Convert sales records to dictionary + """ + invoice = sale.invoice sale_features = [x for x in SALE_FEATURES if x in features] course_reg_features = [x for x in COURSE_REGISTRATION_FEATURES if x in features] # Extracting sale information - sale_dict = dict((feature, getattr(sale, feature)) + sale_dict = dict((feature, getattr(invoice, feature)) for feature in sale_features) total_used_codes = RegistrationCodeRedemption.objects.filter( registration_code__in=sale.courseregistrationcode_set.all() ).count() - sale_dict.update({"invoice_number": getattr(sale, 'id')}) + sale_dict.update({"invoice_number": getattr(invoice, 'id')}) sale_dict.update({"total_codes": sale.courseregistrationcode_set.all().count()}) sale_dict.update({'total_used_codes': total_used_codes}) @@ -261,11 +264,11 @@ def course_registration_features(features, registration_codes, csv_type): course_registration_dict = dict((feature, getattr(registration_code, feature)) for feature in registration_features) course_registration_dict['company_name'] = None - if registration_code.invoice: - course_registration_dict['company_name'] = getattr(registration_code.invoice, 'company_name') + if registration_code.invoice_item: + course_registration_dict['company_name'] = getattr(registration_code.invoice_item.invoice, 'company_name') course_registration_dict['redeemed_by'] = None - if registration_code.invoice: - sale_invoice = Invoice.objects.get(id=registration_code.invoice_id) + if registration_code.invoice_item: + sale_invoice = registration_code.invoice_item.invoice course_registration_dict['invoice_id'] = sale_invoice.id course_registration_dict['purchaser'] = sale_invoice.recipient_name course_registration_dict['customer_reference_number'] = sale_invoice.customer_reference_number diff --git a/lms/djangoapps/instructor_analytics/tests/test_basic.py b/lms/djangoapps/instructor_analytics/tests/test_basic.py index 6d2a6ab905..dc43788600 100644 --- a/lms/djangoapps/instructor_analytics/tests/test_basic.py +++ b/lms/djangoapps/instructor_analytics/tests/test_basic.py @@ -11,7 +11,7 @@ from student.tests.factories import UserFactory, CourseModeFactory from opaque_keys.edx.locations import SlashSeparatedCourseKey from shoppingcart.models import ( CourseRegistrationCode, RegistrationCodeRedemption, Order, - Invoice, Coupon, CourseRegCodeItem, CouponRedemption + Invoice, Coupon, CourseRegCodeItem, CouponRedemption, CourseRegistrationCodeInvoiceItem ) from course_modes.models import CourseMode from instructor_analytics.basic import ( @@ -147,10 +147,16 @@ class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase): company_contact_email='test@company.com', recipient_name='Testw_1', recipient_email='test2@test.com', customer_reference_number='2Fwe23S', internal_reference="ABC", course_id=self.course.id ) + invoice_item = CourseRegistrationCodeInvoiceItem.objects.create( + invoice=sale_invoice, + qty=1, + unit_price=1234.32, + course_id=self.course.id + ) for i in range(5): course_code = CourseRegistrationCode( code="test_code{}".format(i), course_id=self.course.id.to_deprecated_string(), - created_by=self.instructor, invoice=sale_invoice, mode_slug='honor' + created_by=self.instructor, invoice=sale_invoice, invoice_item=invoice_item, mode_slug='honor' ) course_code.save() @@ -272,7 +278,7 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase): kwargs={'course_id': self.course.id.to_deprecated_string()}) data = { - 'total_registration_codes': 12, 'company_name': 'Test Group', 'sale_price': 122.45, + 'total_registration_codes': 12, 'company_name': 'Test Group', 'unit_price': 122.45, 'company_contact_name': 'TestName', 'company_contact_email': 'test@company.com', 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', @@ -306,11 +312,17 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase): ) self.assertIn( course_registration['company_name'], - [getattr(registration_code.invoice, 'company_name') for registration_code in registration_codes] + [ + getattr(registration_code.invoice_item.invoice, 'company_name') + for registration_code in registration_codes + ] ) self.assertIn( course_registration['invoice_id'], - [registration_code.invoice_id for registration_code in registration_codes] + [ + registration_code.invoice_item.invoice_id + for registration_code in registration_codes + ] ) def test_coupon_codes_features(self): diff --git a/lms/djangoapps/shoppingcart/admin.py b/lms/djangoapps/shoppingcart/admin.py index 8f68b098c2..fae033a849 100644 --- a/lms/djangoapps/shoppingcart/admin.py +++ b/lms/djangoapps/shoppingcart/admin.py @@ -1,9 +1,12 @@ -""" -Allows django admin site to add PaidCourseRegistrationAnnotations -""" +"""Django admin interface for the shopping cart models. """ from ratelimitbackend import admin from shoppingcart.models import ( - PaidCourseRegistrationAnnotation, Coupon, DonationConfiguration + PaidCourseRegistrationAnnotation, + Coupon, + DonationConfiguration, + Invoice, + CourseRegistrationCodeInvoiceItem, + InvoiceTransaction ) @@ -49,6 +52,128 @@ class SoftDeleteCouponAdmin(admin.ModelAdmin): really_delete_selected.short_description = "Delete s selected entries" + +class CourseRegistrationCodeInvoiceItemInline(admin.StackedInline): + """Admin for course registration code invoice items. + + Displayed inline within the invoice admin UI. + """ + model = CourseRegistrationCodeInvoiceItem + extra = 0 + can_delete = False + readonly_fields = ( + 'qty', + 'unit_price', + 'currency', + 'course_id', + ) + + def has_add_permission(self, request): + return False + + +class InvoiceTransactionInline(admin.StackedInline): + """Admin for invoice transactions. + + Displayed inline within the invoice admin UI. + """ + model = InvoiceTransaction + extra = 0 + readonly_fields = ( + 'created', + 'modified', + 'created_by', + 'last_modified_by' + ) + + +class InvoiceAdmin(admin.ModelAdmin): + """Admin for invoices. + + This is intended for the internal finance team + to be able to view and update invoice information, + including payments and refunds. + + """ + date_hierarchy = 'created' + can_delete = False + readonly_fields = ('created', 'modified') + search_fields = ( + 'internal_reference', + 'customer_reference_number', + 'company_name', + ) + fieldsets = ( + ( + None, { + 'fields': ( + 'internal_reference', + 'customer_reference_number', + 'created', + 'modified', + ) + } + ), + ( + 'Billing Information', { + 'fields': ( + 'company_name', + 'company_contact_name', + 'company_contact_email', + 'recipient_name', + 'recipient_email', + 'address_line_1', + 'address_line_2', + 'address_line_3', + 'city', + 'state', + 'zip', + 'country' + ) + } + ) + ) + readonly_fields = ( + 'internal_reference', + 'customer_reference_number', + 'created', + 'modified', + 'company_name', + 'company_contact_name', + 'company_contact_email', + 'recipient_name', + 'recipient_email', + 'address_line_1', + 'address_line_2', + 'address_line_3', + 'city', + 'state', + 'zip', + 'country' + ) + inlines = [ + CourseRegistrationCodeInvoiceItemInline, + InvoiceTransactionInline + ] + + def save_formset(self, request, form, formset, change): + """Save the user who created and modified invoice transactions. """ + instances = formset.save(commit=False) + for instance in instances: + if isinstance(instance, InvoiceTransaction): + if not hasattr(instance, 'created_by'): + instance.created_by = request.user + instance.last_modified_by = request.user + instance.save() + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + admin.site.register(PaidCourseRegistrationAnnotation) admin.site.register(Coupon, SoftDeleteCouponAdmin) admin.site.register(DonationConfiguration) +admin.site.register(Invoice, InvoiceAdmin) diff --git a/lms/djangoapps/shoppingcart/migrations/0025_update_invoice_models.py b/lms/djangoapps/shoppingcart/migrations/0025_update_invoice_models.py new file mode 100644 index 0000000000..5dfcc3c023 --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0025_update_invoice_models.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'InvoiceTransaction' + db.create_table('shoppingcart_invoicetransaction', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)), + ('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)), + ('invoice', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['shoppingcart.Invoice'])), + ('amount', self.gf('django.db.models.fields.DecimalField')(default=0.0, max_digits=30, decimal_places=2)), + ('currency', self.gf('django.db.models.fields.CharField')(default='usd', max_length=8)), + ('comments', self.gf('django.db.models.fields.TextField')(null=True)), + ('status', self.gf('django.db.models.fields.CharField')(default='started', max_length=32)), + ('created_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('last_modified_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='last_modified_by_user', to=orm['auth.User'])), + )) + db.send_create_signal('shoppingcart', ['InvoiceTransaction']) + + # Adding model 'InvoiceItem' + db.create_table('shoppingcart_invoiceitem', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)), + ('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)), + ('invoice', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['shoppingcart.Invoice'])), + ('qty', self.gf('django.db.models.fields.IntegerField')(default=1)), + ('unit_price', self.gf('django.db.models.fields.DecimalField')(default=0.0, max_digits=30, decimal_places=2)), + ('currency', self.gf('django.db.models.fields.CharField')(default='usd', max_length=8)), + )) + db.send_create_signal('shoppingcart', ['InvoiceItem']) + + # Adding model 'CourseRegistrationCodeInvoiceItem' + db.create_table('shoppingcart_courseregistrationcodeinvoiceitem', ( + ('invoiceitem_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['shoppingcart.InvoiceItem'], unique=True, primary_key=True)), + ('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=128, db_index=True)), + )) + db.send_create_signal('shoppingcart', ['CourseRegistrationCodeInvoiceItem']) + + # Adding field 'CourseRegistrationCode.invoice_item' + db.add_column('shoppingcart_courseregistrationcode', 'invoice_item', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['shoppingcart.CourseRegistrationCodeInvoiceItem'], null=True), + keep_default=True) + + # Adding field 'Invoice.created' + db.add_column('shoppingcart_invoice', 'created', + self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now), + keep_default=True) + + # Adding field 'Invoice.modified' + db.add_column('shoppingcart_invoice', 'modified', + self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now), + keep_default=True) + + + def backwards(self, orm): + # Deleting model 'InvoiceTransaction' + db.delete_table('shoppingcart_invoicetransaction') + + # Deleting model 'InvoiceItem' + db.delete_table('shoppingcart_invoiceitem') + + # Deleting model 'CourseRegistrationCodeInvoiceItem' + db.delete_table('shoppingcart_courseregistrationcodeinvoiceitem') + + # Deleting field 'CourseRegistrationCode.invoice_item' + db.delete_column('shoppingcart_courseregistrationcode', 'invoice_item_id') + + # Deleting field 'Invoice.created' + db.delete_column('shoppingcart_invoice', 'created') + + # Deleting field 'Invoice.modified' + db.delete_column('shoppingcart_invoice', 'modified') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 27, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregcodeitem': { + 'Meta': {'object_name': 'CourseRegCodeItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.courseregcodeitemannotation': { + 'Meta': {'object_name': 'CourseRegCodeItemAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 27, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}), + 'invoice_item': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCodeInvoiceItem']", 'null': 'True'}), + 'mode_slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'purchase_order'", 'null': 'True', 'to': "orm['shoppingcart.Order']"}) + }, + 'shoppingcart.courseregistrationcodeinvoiceitem': { + 'Meta': {'object_name': 'CourseRegistrationCodeInvoiceItem', '_ormbases': ['shoppingcart.InvoiceItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'invoiceitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.InvoiceItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.donation': { + 'Meta': {'object_name': 'Donation', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'donation_type': ('django.db.models.fields.CharField', [], {'default': "'general'", 'max_length': '32'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.donationconfiguration': { + 'Meta': {'object_name': 'DonationConfiguration'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}), + 'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}) + }, + 'shoppingcart.invoiceitem': { + 'Meta': {'object_name': 'InvoiceItem'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'unit_price': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}) + }, + 'shoppingcart.invoicetransaction': { + 'Meta': {'object_name': 'InvoiceTransaction'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}), + 'last_modified_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_modified_by_user'", 'to': "orm['auth.User']"}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'started'", 'max_length': '32'}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order_type': ('django.db.models.fields.CharField', [], {'default': "'personal'", 'max_length': '32'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']", 'null': 'True'}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 27, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/migrations/0026_migrate_invoices.py b/lms/djangoapps/shoppingcart/migrations/0026_migrate_invoices.py new file mode 100644 index 0000000000..787c8907f1 --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0026_migrate_invoices.py @@ -0,0 +1,270 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models +from decimal import Decimal, ROUND_DOWN + +class Migration(DataMigration): + + def forwards(self, orm): + # Select all the invoices and number of registration codes(as qty) associated with every invoice + invoices = orm.Invoice.objects.extra( + select={ + 'qty': 'SELECT COUNT(*) FROM shoppingcart_courseregistrationcode ' + 'WHERE shoppingcart_courseregistrationcode.invoice_id = shoppingcart_invoice.id' + } + ).all() + for invoice in invoices: + invoice_item = self._create_invoice_item(invoice, orm) + orm.CourseRegistrationCode.objects.filter(invoice=invoice).update(invoice_item=invoice_item) + + def backwards(self, orm): + """ + We don't need the backward migration because the data is already there in old models and + schema rollback will automatically remove this new data. + """ + pass + + @staticmethod + def _create_invoice_item(invoice, orm): + unit_price = ((Decimal(invoice.total_amount))/invoice.qty).quantize(Decimal('.01'), rounding=ROUND_DOWN) + invoice_item = orm.CourseRegistrationCodeInvoiceItem.objects.create( + invoice=invoice, + qty=invoice.qty, + unit_price=unit_price, + course_id=invoice.course_id + ) + return invoice_item + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 27, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregcodeitem': { + 'Meta': {'object_name': 'CourseRegCodeItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.courseregcodeitemannotation': { + 'Meta': {'object_name': 'CourseRegCodeItemAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 27, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}), + 'invoice_item': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCodeInvoiceItem']", 'null': 'True'}), + 'mode_slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'purchase_order'", 'null': 'True', 'to': "orm['shoppingcart.Order']"}) + }, + 'shoppingcart.courseregistrationcodeinvoiceitem': { + 'Meta': {'object_name': 'CourseRegistrationCodeInvoiceItem', '_ormbases': ['shoppingcart.InvoiceItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'invoiceitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.InvoiceItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.donation': { + 'Meta': {'object_name': 'Donation', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'donation_type': ('django.db.models.fields.CharField', [], {'default': "'general'", 'max_length': '32'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.donationconfiguration': { + 'Meta': {'object_name': 'DonationConfiguration'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}), + 'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}) + }, + 'shoppingcart.invoiceitem': { + 'Meta': {'object_name': 'InvoiceItem'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'unit_price': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}) + }, + 'shoppingcart.invoicetransaction': { + 'Meta': {'object_name': 'InvoiceTransaction'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}), + 'last_modified_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_modified_by_user'", 'to': "orm['auth.User']"}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'started'", 'max_length': '32'}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order_type': ('django.db.models.fields.CharField', [], {'default': "'personal'", 'max_length': '32'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']", 'null': 'True'}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 27, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] + symmetrical = True diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 8c6bda7860..dfae72de03 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -18,7 +18,7 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.mail import send_mail from django.contrib.auth.models import User -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext as _, ugettext_lazy from django.db import transaction from django.db.models import Sum from django.core.urlresolvers import reverse @@ -773,11 +773,11 @@ class OrderItem(TimeStampedModel): self.save() -class Invoice(models.Model): +class Invoice(TimeStampedModel): """ - This table capture all the information needed to support "invoicing" - which is when a user wants to purchase Registration Codes, - but will not do so via a Credit Card transaction. + This table capture all the information needed to support "invoicing" + which is when a user wants to purchase Registration Codes, + but will not do so via a Credit Card transaction. """ company_name = models.CharField(max_length=255, db_index=True) company_contact_name = models.CharField(max_length=255) @@ -785,16 +785,39 @@ class Invoice(models.Model): recipient_name = models.CharField(max_length=255) recipient_email = models.CharField(max_length=255) address_line_1 = models.CharField(max_length=255) - address_line_2 = models.CharField(max_length=255, null=True) - address_line_3 = models.CharField(max_length=255, null=True) + address_line_2 = models.CharField(max_length=255, null=True, blank=True) + address_line_3 = models.CharField(max_length=255, null=True, blank=True) city = models.CharField(max_length=255, null=True) state = models.CharField(max_length=255, null=True) zip = models.CharField(max_length=15, null=True) country = models.CharField(max_length=64, null=True) - course_id = CourseKeyField(max_length=255, db_index=True) + + # This field has been deprecated. + # The total amount can now be calculated as the sum + # of each invoice item associated with the invoice. + # For backwards compatibility, this field is maintained + # and written to during invoice creation. total_amount = models.FloatField() - internal_reference = models.CharField(max_length=255, null=True) - customer_reference_number = models.CharField(max_length=63, null=True) + + # This field has been deprecated in order to support + # invoices for items that are not course-related. + # Although this field is still maintained for backwards + # compatibility, you should use CourseRegistrationCodeInvoiceItem + # to look up the course ID for purchased redeem codes. + course_id = CourseKeyField(max_length=255, db_index=True) + + internal_reference = models.CharField( + max_length=255, + null=True, + blank=True, + help_text=ugettext_lazy("Internal reference code for this invoice.") + ) + customer_reference_number = models.CharField( + max_length=63, + null=True, + blank=True, + help_text=ugettext_lazy("Customer's reference code for this invoice.") + ) is_valid = models.BooleanField(default=True) def generate_pdf_invoice(self, course, course_price, quantity, sale_price): @@ -824,6 +847,125 @@ class Invoice(models.Model): return pdf_buffer + def __unicode__(self): + label = ( + unicode(self.internal_reference) + if self.internal_reference + else u"No label" + ) + + created = ( + self.created.strftime("%Y-%m-%d") # pylint: disable=no-member + if self.created + else u"No date" + ) + + return u"{label} ({date_created})".format( + label=label, date_created=created + ) + + +INVOICE_TRANSACTION_STATUSES = ( + # A payment/refund is in process, but money has not yet been transferred + ('started', 'started'), + + # A payment/refund has completed successfully + # This should be set ONLY once money has been successfully exchanged. + ('completed', 'completed'), + + # A payment/refund was promised, but was cancelled before + # money had been transferred. An example would be + # cancelling a refund check before the recipient has + # a chance to deposit it. + ('cancelled', 'cancelled') +) + + +class InvoiceTransaction(TimeStampedModel): + """Record payment and refund information for invoices. + + There are two expected use cases: + + 1) We send an invoice to someone, and they send us a check. + We then manually create an invoice transaction to represent + the payment. + + 2) We send an invoice to someone, and they pay us. Later, we + need to issue a refund for the payment. We manually + create a transaction with a negative amount to represent + the refund. + + """ + invoice = models.ForeignKey(Invoice) + amount = models.DecimalField( + default=0.0, decimal_places=2, max_digits=30, + help_text=ugettext_lazy( + "The amount of the transaction. Use positive amounts for payments" + " and negative amounts for refunds." + ) + ) + currency = models.CharField( + default="usd", + max_length=8, + help_text=ugettext_lazy("Lower-case ISO currency codes") + ) + comments = models.TextField( + null=True, + blank=True, + help_text=ugettext_lazy("Optional: provide additional information for this transaction") + ) + status = models.CharField( + max_length=32, + default='started', + choices=INVOICE_TRANSACTION_STATUSES, + help_text=ugettext_lazy( + "The status of the payment or refund. " + "'started' means that payment is expected, but money has not yet been transferred. " + "'completed' means that the payment or refund was received. " + "'cancelled' means that payment or refund was expected, but was cancelled before money was transferred. " + ) + ) + created_by = models.ForeignKey(User) + last_modified_by = models.ForeignKey(User, related_name='last_modified_by_user') + + +class InvoiceItem(TimeStampedModel): + """ + This is the basic interface for invoice items. + + Each invoice item represents a "line" in the invoice. + For example, in an invoice for course registration codes, + there might be an invoice item representing 10 registration + codes for the DemoX course. + + """ + objects = InheritanceManager() + invoice = models.ForeignKey(Invoice, db_index=True) + qty = models.IntegerField( + default=1, + help_text=ugettext_lazy("The number of items sold.") + ) + unit_price = models.DecimalField( + default=0.0, + decimal_places=2, + max_digits=30, + help_text=ugettext_lazy("The price per item sold, including discounts.") + ) + currency = models.CharField( + default="usd", + max_length=8, + help_text=ugettext_lazy("Lower-case ISO currency codes") + ) + + +class CourseRegistrationCodeInvoiceItem(InvoiceItem): + """ + This is an invoice item that represents a payment for + a course registration. + + """ + course_id = CourseKeyField(max_length=128, db_index=True) + class CourseRegistrationCode(models.Model): """ @@ -835,9 +977,14 @@ class CourseRegistrationCode(models.Model): created_by = models.ForeignKey(User, related_name='created_by_user') created_at = models.DateTimeField(default=datetime.now(pytz.utc)) order = models.ForeignKey(Order, db_index=True, null=True, related_name="purchase_order") - invoice = models.ForeignKey(Invoice, null=True) mode_slug = models.CharField(max_length=100, null=True) + # For backwards compatibility, we maintain the FK to "invoice" + # In the future, we will remove this in favor of the FK + # to "invoice_item" (which can be used to look up the invoice). + invoice = models.ForeignKey(Invoice, null=True) + invoice_item = models.ForeignKey(CourseRegistrationCodeInvoiceItem, null=True) + class RegistrationCodeRedemption(models.Model): """ @@ -1227,7 +1374,7 @@ class CourseRegCodeItem(OrderItem): # is in another PR (for another feature) from instructor.views.api import save_registration_code for i in range(total_registration_codes): # pylint: disable=unused-variable - save_registration_code(self.user, self.course_id, self.mode, invoice=None, order=self.order) + save_registration_code(self.user, self.course_id, self.mode, order=self.order) log.info("Enrolled {0} in paid course {1}, paid ${2}" .format(self.user.email, self.course_id, self.line_cost)) # pylint: disable=no-member diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index a729f18e05..afa9f58486 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -1518,7 +1518,7 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase): data = { 'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com', - 'company_contact_email': 'Test@company.com', 'sale_price': 122.45, 'recipient_name': 'Test123', + 'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123', 'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '', 'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': '' diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 789145c8b0..8fddfd6b00 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -1664,7 +1664,7 @@ input[name="subject"] { height: auto; } } - li#generate-registration-modal-field-country ~ li#generate-registration-modal-field-total-price, + li#generate-registration-modal-field-country ~ li#generate-registration-modal-field-unit-price, li#generate-registration-modal-field-country ~ li#generate-registration-modal-field-internal-reference { @include margin-left(0px !important); @include margin-right(15px !important); diff --git a/lms/templates/instructor/instructor_dashboard_2/e-commerce.html b/lms/templates/instructor/instructor_dashboard_2/e-commerce.html index 1d96a064bb..291b363bc7 100644 --- a/lms/templates/instructor/instructor_dashboard_2/e-commerce.html +++ b/lms/templates/instructor/instructor_dashboard_2/e-commerce.html @@ -296,7 +296,7 @@ var total_registration_codes = $('input[name="total_registration_codes"]').val(); var recipient_name = $('input[name="recipient_name"]').val(); var recipient_email = $('input[name="recipient_email"]').val(); - var sale_price = $('input[name="sale_price"]').val(); + var unit_price = $('input[name="unit_price"]').val(); var company_name = $('input[name="company_name"]').val(); var company_contact_name = $('input[name="company_contact_name"]').val(); var company_contact_email = $('input[name="company_contact_email"]').val(); @@ -305,91 +305,91 @@ if (company_name == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the company name'); + registration_code_error.text("${_('Please enter the company name')}"); generate_registration_button.removeAttr('disabled'); return false; } if (($.isNumeric(company_name))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the non-numeric value for company name'); + registration_code_error.text("${_('Please enter the non-numeric value for company name')}"); generate_registration_button.removeAttr('disabled'); return false; } if (company_contact_name == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the company contact name'); + registration_code_error.text("${_('Please enter the company contact name')}"); generate_registration_button.removeAttr('disabled'); return false; } if (($.isNumeric(company_contact_name))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the non-numeric value for company contact name'); + registration_code_error.text("${_('Please enter the non-numeric value for company contact name')}"); generate_registration_button.removeAttr('disabled'); return false; } if (company_contact_email == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the company contact email'); + registration_code_error.text("${_('Please enter the company contact email')}"); generate_registration_button.removeAttr('disabled'); return false; } if (!(validateEmail(company_contact_email))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the valid email address'); + registration_code_error.text("${_('Please enter the valid email address')}"); generate_registration_button.removeAttr('disabled'); return false; } if (recipient_name == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the recipient name'); + registration_code_error.text("${_('Please enter the recipient name')}"); generate_registration_button.removeAttr('disabled'); return false; } if (($.isNumeric(recipient_name))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the non-numeric value for recipient name'); + registration_code_error.text("${_('Please enter the non-numeric value for recipient name')}"); generate_registration_button.removeAttr('disabled'); return false; } if (recipient_email == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the recipient email'); + registration_code_error.text("${_('Please enter the recipient email')}"); generate_registration_button.removeAttr('disabled'); return false; } if (!(validateEmail(recipient_email))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the valid email address'); + registration_code_error.text("${_('Please enter the valid email address')}"); generate_registration_button.removeAttr('disabled'); return false; } if (address_line == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the billing address'); + registration_code_error.text("${_('Please enter the billing address')}"); generate_registration_button.removeAttr('disabled'); return false; } - if (sale_price == '') { + if (unit_price == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the sale price'); + registration_code_error.text("${_('Please enter the unit price')}"); generate_registration_button.removeAttr('disabled'); return false } - if (!($.isNumeric(sale_price))) { + if (!($.isNumeric(unit_price))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the numeric value for sale price'); + registration_code_error.text("${_('Please enter the numeric value for unit price')}"); generate_registration_button.removeAttr('disabled'); return false } if (total_registration_codes == '') { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the total registration codes'); + registration_code_error.text("${_('Please enter the number of enrollment codes')}"); generate_registration_button.removeAttr('disabled'); return false } if (!($.isNumeric(total_registration_codes))) { registration_code_error.attr('style', 'display: block !important'); - registration_code_error.text('Please enter the numeric value for total registration codes'); + registration_code_error.text("${_('Please enter the numeric value for number of enrollment codes')}"); generate_registration_button.removeAttr('disabled'); return false; } @@ -600,7 +600,7 @@ $('input[name="country"]').val(''); $('input[name="customer_reference_number"]').val(''); $('input[name="recipient_name"]').val(''); - $('input[name="sale_price"]').val(''); + $('input[name="unit_price"]').val(''); $('input[name="recipient_email"]').val(''); $('input[name="company_contact_name"]').val(''); $('input[name="company_contact_email"]').val(''); diff --git a/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html b/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html index 8731880199..c35693dc6a 100644 --- a/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html +++ b/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html @@ -102,12 +102,12 @@
-