Commit 4f7c4949 by asadiqbal08 Committed by Chris Dodge

EX-71 New shopping Cart UI Single person purchase

Ex-74 Registration Code redemption

fix the translation issues

added a check if a user is already registered in a course. Changed the messages

added course depth=0 and removed pep8 violations

Ex-72-added additional billing information

Added a new CSV file in the instructor dashboard sales tab to download all the order sales separated from the invoice sales

fix path to image

updated the failed unit tests and add some minor tweaks in the Shoppingcart.scss

Ex-78 New UI In receipt page

EX-73 Purchasers can buy a course on behalf of a different student

WL-78 updated the receipt page UI.

Wl-72 updated Billing Information UI and removed the Order Address fields

WL-71 Remove Purchase Type Buttons from UI

WL-71 Updated minor UI issues and updated test cases

WL-78 updated the enrollment links in the receipt page

made changes in Order generated sales csv in Instructor Dashboard.
 The total_registration_codes and total_used_codes
  were not correctly stored in the csv file.

1) The total_registration_codes were not filtered with
 course_id.
2) The total_used_codes that a user had redeemed
 were not correctly included in the CSV.

added a fix in the courseware view to let the users visit the courseware if they have enrolled in the course by clicking on the enrollment link

rebase and resolved conflicts with master

WL-97 Bulk Registration Email Confirmation
Below is the commit summary.
- Make email text bold as per requirement.
- Improve email template quality and reorder points.
- Add text in billing details page : "if no additional billing details are populated the payment confirmation will be sent to the user making the purchase"
- Update text on receipt page "You have successfully purchase 3 course registration codes"

WL-100 fixed the bug on the edit/add coupon and set course price.
Ajax requests were duplicating in each callback. fixed this issue by creating the manual ajax request rather than the Lean Modal Ajax requests

allow for better White Label branding in shopping cart purchase emails

fix up typos and text

fix goof

fix

fix

incorporated model changes as suggested by Jason.

updated order sales csv

updated test cases for CourseRegCodeItem model and csv for the order generated sales

updated the migrations history

fixed the lms acceptance tests

Be sure to check for multiple types

address PR feedback

PR feedback

PR feedback

pep8 fix
parent 5a31005e
......@@ -446,13 +446,17 @@ def is_course_blocked(request, redeemed_registration_codes, course_key):
"""Checking either registration is blocked or not ."""
blocked = False
for redeemed_registration in redeemed_registration_codes:
if not getattr(redeemed_registration.invoice, 'is_valid'):
blocked = True
# disabling email notifications for unpaid registration courses
Optout.objects.get_or_create(user=request.user, course_id=course_key)
log.info(u"User {0} ({1}) opted out of receiving emails from course {2}".format(request.user.username, request.user.email, course_key))
track.views.server_track(request, "change-email1-settings", {"receive_emails": "no", "course": course_key.to_deprecated_string()}, page='dashboard')
break
# 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'):
blocked = True
# disabling email notifications for unpaid registration courses
Optout.objects.get_or_create(user=request.user, course_id=course_key)
log.info(u"User {0} ({1}) opted out of receiving emails from course {2}".format(request.user.username, request.user.email, course_key))
track.views.server_track(request, "change-email1-settings", {"receive_emails": "no", "course": course_key.to_deprecated_string()}, page='dashboard')
break
return blocked
......
......@@ -39,7 +39,7 @@ from course_modes.models import CourseMode
from open_ended_grading import open_ended_notifications
from student.models import UserTestGroup, CourseEnrollment
from student.views import single_course_reverification_info
from student.views import single_course_reverification_info, is_course_blocked
from util.cache import cache, cache_if_anonymous
from xblock.fragment import Fragment
from xmodule.modulestore.django import modulestore
......@@ -277,12 +277,22 @@ def index(request, course_id, chapter=None, section=None,
user = User.objects.prefetch_related("groups").get(id=request.user.id)
# Redirecting to dashboard if the course is blocked due to un payment.
redeemed_registration_codes = CourseRegistrationCode.objects.filter(course_id=course_key, registrationcoderedemption__redeemed_by=request.user)
for redeemed_registration in redeemed_registration_codes:
if not getattr(redeemed_registration.invoice, 'is_valid'):
log.warning(u'User %s cannot access the course %s because payment has not yet been received', user, course_key.to_deprecated_string())
return redirect(reverse('dashboard'))
redeemed_registration_codes = CourseRegistrationCode.objects.filter(
course_id=course_key,
registrationcoderedemption__redeemed_by=request.user
)
# Redirect to dashboard if the course is blocked due to non-payment.
if 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
log.warning(
u'User %s cannot access the course %s because payment has not yet been received',
user,
course_key.to_deprecated_string()
)
return redirect(reverse('dashboard'))
request.user = user # keep just one instance of User
with modulestore().bulk_operations(course_key):
......@@ -703,7 +713,8 @@ def course_about(request, course_id):
settings.PAID_COURSE_REGISTRATION_CURRENCY[0])
if request.user.is_authenticated():
cart = shoppingcart.models.Order.get_cart_for_user(request.user)
in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_key)
in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_key) or \
shoppingcart.models.CourseRegCodeItem.contained_in_order(cart, course_key)
reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
reg_url=reverse('register_user'), course_id=course.id.to_deprecated_string())
......
......@@ -1448,6 +1448,21 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
response = self.client.get(url + '/csv', {})
self.assertEqual(response['Content-Type'], 'text/csv')
def test_get_sale_order_records_features_csv(self):
"""
Test that the response from get_sale_order_records is in csv format.
"""
self.cart.order_type = 'business'
self.cart.save()
self.cart.add_billing_details(company_name='Test Company', company_contact_name='Test',
company_contact_email='test@123', recipient_name='R1',
recipient_email='', customer_reference_number='PO#23')
PaidCourseRegistration.add_to_order(self.cart, self.course.id)
self.cart.purchase()
sale_order_url = reverse('get_sale_order_records', kwargs={'course_id': self.course.id.to_deprecated_string()})
response = self.client.get(sale_order_url)
self.assertEqual(response['Content-Type'], 'text/csv')
def test_get_sale_records_features_csv(self):
"""
Test that the response from get_sale_records is in csv format.
......
......@@ -588,7 +588,47 @@ def get_sale_records(request, course_id, csv=False): # pylint: disable=W0613, W
return JsonResponse(response_payload)
else:
header, datarows = instructor_analytics.csvs.format_dictlist(sale_data, query_features)
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_records.csv", header, datarows)
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_invoice_records.csv", header, datarows)
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
def get_sale_order_records(request, course_id): # pylint: disable=W0613, W0621
"""
return the summary of all sales records for a particular course
"""
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
query_features = [
('id', 'Order Id'),
('company_name', 'Company Name'),
('company_contact_name', 'Company Contact Name'),
('company_contact_email', 'Company Contact Email'),
('total_amount', 'Total Amount'),
('total_codes', 'Total Codes'),
('total_used_codes', 'Total Used Codes'),
('logged_in_username', 'Login Username'),
('logged_in_email', 'Login User Email'),
('purchase_time', 'Date of Sale'),
('customer_reference_number', 'Customer Reference Number'),
('recipient_name', 'Recipient Name'),
('recipient_email', 'Recipient Email'),
('bill_to_street1', 'Street 1'),
('bill_to_street2', 'Street 2'),
('bill_to_city', 'City'),
('bill_to_state', 'State'),
('bill_to_postalcode', 'Postal Code'),
('bill_to_country', 'Country'),
('order_type', 'Order Type'),
('codes', 'Registration Codes'),
('course_id', 'Course Id')
]
db_columns = [x[0] for x in query_features]
csv_columns = [x[1] for x in query_features]
sale_data = instructor_analytics.basic.sale_order_record_features(course_id, db_columns)
header, datarows = instructor_analytics.csvs.format_dictlist(sale_data, db_columns) # pylint: disable=W0612
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_order_records.csv", csv_columns, datarows)
@require_level('staff')
......@@ -766,7 +806,7 @@ def get_coupon_codes(request, course_id): # pylint: disable=W0613
return instructor_analytics.csvs.create_csv_response('Coupons.csv', header, data_rows)
def save_registration_codes(request, course_id, generated_codes_list, invoice):
def save_registration_code(user, course_id, invoice=None, order=None):
"""
recursive function that generate a new code every time and saves in the Course Registration Table
if validation check passes
......@@ -776,16 +816,16 @@ def save_registration_codes(request, course_id, generated_codes_list, invoice):
# 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_codes(request, course_id, generated_codes_list, invoice)
return save_registration_code(user, course_id, invoice, order)
course_registration = CourseRegistrationCode(
code=code, course_id=course_id.to_deprecated_string(), created_by=request.user, invoice=invoice
code=code, course_id=course_id.to_deprecated_string(), created_by=user, invoice=invoice, order=order
)
try:
course_registration.save()
generated_codes_list.append(course_registration)
return course_registration
except IntegrityError:
return save_registration_codes(request, course_id, generated_codes_list, invoice)
return save_registration_code(user, course_id, invoice, order)
def registration_codes_csv(file_name, codes_list, csv_type=None):
......@@ -851,7 +891,6 @@ def generate_registration_codes(request, course_id):
Respond with csv which contains a summary of all Generated Codes.
"""
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course_registration_codes = []
invoice_copy = False
# covert the course registration code number into integer
......@@ -888,8 +927,10 @@ def generate_registration_codes(request, course_id):
address_line_3=address_line_3, city=city, state=state, zip=zip_code, country=country,
internal_reference=internal_reference, customer_reference_number=customer_reference_number
)
registration_codes = []
for _ in range(course_code_number): # pylint: disable=W0621
save_registration_codes(request, course_id, course_registration_codes, sale_invoice)
generated_registration_code = save_registration_code(request.user, course_id, sale_invoice, order=None)
registration_codes.append(generated_registration_code)
site_name = microsite.get_value('SITE_NAME', 'localhost')
course = get_course_by_id(course_id, depth=None)
......@@ -916,7 +957,7 @@ def generate_registration_codes(request, course_id):
'discount': discount,
'sale_price': sale_price,
'quantity': quantity,
'registration_codes': course_registration_codes,
'registration_codes': registration_codes,
'course_url': course_url,
'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME),
'dashboard_url': dashboard_url,
......@@ -934,7 +975,7 @@ def generate_registration_codes(request, course_id):
#send_mail(subject, message, from_address, recipient_list, fail_silently=False)
csv_file = StringIO.StringIO()
csv_writer = csv.writer(csv_file)
for registration_code in course_registration_codes:
for registration_code in registration_codes:
csv_writer.writerow([registration_code.code])
# send a unique email for each recipient, don't put all email addresses in a single email
......@@ -948,7 +989,7 @@ def generate_registration_codes(request, course_id):
email.attach(u'Invoice.txt', invoice_attachment, 'text/plain')
email.send()
return registration_codes_csv("Registration_Codes.csv", course_registration_codes)
return registration_codes_csv("Registration_Codes.csv", registration_codes)
@ensure_csrf_cookie
......
......@@ -23,6 +23,8 @@ urlpatterns = patterns('', # nopep8
'instructor.views.api.get_user_invoice_preference', name="get_user_invoice_preference"),
url(r'^get_sale_records(?P<csv>/csv)?$',
'instructor.views.api.get_sale_records', name="get_sale_records"),
url(r'^get_sale_order_records$',
'instructor.views.api.get_sale_order_records', name="get_sale_order_records"),
url(r'^sale_validation_url$',
'instructor.views.api.sale_validation', name="sale_validation"),
url(r'^get_anon_ids$',
......
......@@ -62,27 +62,36 @@ def add_coupon(request, course_id): # pylint: disable=W0613
# check if the coupon code is in the CourseRegistrationCode Table
course_registration_code = CourseRegistrationCode.objects.filter(code=code)
if course_registration_code:
return HttpResponseNotFound(_(
"The code ({code}) that you have tried to define is already in use as a registration code").format(code=code)
)
return JsonResponse(
{'message': _("The code ({code}) that you have tried to define is already in use as a registration code").format(code=code)},
status=400) # status code 400: Bad Request
description = request.POST.get('description')
course_id = request.POST.get('course_id')
try:
discount = int(request.POST.get('discount'))
except ValueError:
return HttpResponseNotFound(_("Please Enter the Integer Value for Coupon Discount"))
return JsonResponse({
'message': _("Please Enter the Integer Value for Coupon Discount")
}, status=400) # status code 400: Bad Request
if discount > 100 or discount < 0:
return HttpResponseNotFound(_("Please Enter the Coupon Discount Value Less than or Equal to 100"))
return JsonResponse({
'message': _("Please Enter the Coupon Discount Value Less than or Equal to 100")
}, status=400) # status code 400: Bad Request
coupon = Coupon(
code=code, description=description, course_id=course_id,
percentage_discount=discount, created_by_id=request.user.id
)
coupon.save()
return HttpResponse(_("coupon with the coupon code ({code}) added successfully").format(code=code))
return JsonResponse(
{'message': _("coupon with the coupon code ({code}) added successfully").format(code=code)}
)
if coupon:
return HttpResponseNotFound(_("coupon with the coupon code ({code}) already exists for this course").format(code=code))
return JsonResponse(
{'message': _("coupon with the coupon code ({code}) already exists for this course").format(code=code)},
status=400) # status code 400: Bad Request
@require_POST
......@@ -93,17 +102,21 @@ def update_coupon(request, course_id): # pylint: disable=W0613
"""
coupon_id = request.POST.get('coupon_id', None)
if not coupon_id:
return HttpResponseNotFound(_("coupon id not found"))
return JsonResponse({'message': _("coupon id not found")}, status=400) # status code 400: Bad Request
try:
coupon = Coupon.objects.get(pk=coupon_id)
except ObjectDoesNotExist:
return HttpResponseNotFound(_("coupon with the coupon id ({coupon_id}) DoesNotExist").format(coupon_id=coupon_id))
return JsonResponse(
{'message': _("coupon with the coupon id ({coupon_id}) DoesNotExist").format(coupon_id=coupon_id)},
status=400) # status code 400: Bad Request
description = request.POST.get('description')
coupon.description = description
coupon.save()
return HttpResponse(_("coupon with the coupon id ({coupon_id}) updated Successfully").format(coupon_id=coupon_id))
return JsonResponse(
{'message': _("coupon with the coupon id ({coupon_id}) updated Successfully").format(coupon_id=coupon_id)}
)
@require_POST
......
......@@ -17,6 +17,7 @@ from django.core.urlresolvers import reverse
from django.utils.html import escape
from django.http import Http404, HttpResponse, HttpResponseNotFound
from django.conf import settings
from util.json_request import JsonResponse
from lms.lib.xblock.runtime import quote_slashes
from xmodule_modifiers import wrap_xblock
......@@ -158,6 +159,7 @@ def _section_e_commerce(course, access):
'ajax_add_coupon': reverse('add_coupon', kwargs={'course_id': course_key.to_deprecated_string()}),
'get_purchase_transaction_url': reverse('get_purchase_transaction', kwargs={'course_id': course_key.to_deprecated_string()}),
'get_sale_records_url': reverse('get_sale_records', kwargs={'course_id': course_key.to_deprecated_string()}),
'get_sale_order_records_url': reverse('get_sale_order_records', kwargs={'course_id': course_key.to_deprecated_string()}),
'instructor_url': reverse('instructor_dashboard', kwargs={'course_id': course_key.to_deprecated_string()}),
'get_registration_code_csv_url': reverse('get_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}),
'generate_registration_code_csv_url': reverse('generate_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}),
......@@ -183,15 +185,19 @@ def set_course_mode_price(request, course_id):
try:
course_price = int(request.POST['course_price'])
except ValueError:
return HttpResponseNotFound(_("Please Enter the numeric value for the course price"))
return JsonResponse(
{'message': _("Please Enter the numeric value for the course price")},
status=400) # status code 400: Bad Request
currency = request.POST['currency']
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course_honor_mode = CourseMode.objects.filter(mode_slug='honor', course_id=course_key)
if not course_honor_mode:
return HttpResponseNotFound(
_("CourseMode with the mode slug({mode_slug}) DoesNotExist").format(mode_slug='honor')
)
return JsonResponse(
{'message': _("CourseMode with the mode slug({mode_slug}) DoesNotExist").format(mode_slug='honor')},
status=400) # status code 400: Bad Request
CourseModesArchive.objects.create(
course_id=course_id, mode_slug='honor', mode_display_name='Honor Code Certificate',
min_price=getattr(course_honor_mode[0], 'min_price'), currency=getattr(course_honor_mode[0], 'currency'),
......@@ -201,7 +207,7 @@ def set_course_mode_price(request, course_id):
min_price=course_price,
currency=currency
)
return HttpResponse(_("CourseMode price updated successfully"))
return JsonResponse({'message': _("CourseMode price updated successfully")})
def _section_course_info(course, access):
......
......@@ -3,7 +3,10 @@ Student and course analytics.
Serve miscellaneous course and student data
"""
from shoppingcart.models import PaidCourseRegistration, CouponRedemption, Invoice, RegistrationCodeRedemption
from shoppingcart.models import (
PaidCourseRegistration, CouponRedemption, Invoice, CourseRegCodeItem,
OrderTypes, RegistrationCodeRedemption, CourseRegistrationCode
)
from django.contrib.auth.models import User
import xmodule.graders as xmgraders
from django.core.exceptions import ObjectDoesNotExist
......@@ -18,11 +21,77 @@ ORDER_FEATURES = ('purchase_time',)
SALE_FEATURES = ('total_amount', 'company_name', 'company_contact_name', 'company_contact_email', 'recipient_name',
'recipient_email', 'customer_reference_number', 'internal_reference')
SALE_ORDER_FEATURES = ('id', 'company_name', 'company_contact_name', 'company_contact_email', 'purchase_time',
'customer_reference_number', 'recipient_name', 'recipient_email', 'bill_to_street1',
'bill_to_street2', 'bill_to_city', 'bill_to_state', 'bill_to_postalcode',
'bill_to_country', 'order_type',)
AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at')
COUPON_FEATURES = ('course_id', 'percentage_discount', 'description')
def sale_order_record_features(course_id, features):
"""
Return list of sale orders features as dictionaries.
sales_records(course_id, ['company_name, total_codes', total_amount])
would return [
{'company_name': 'group_A', 'total_codes': '1', total_amount:'total_amount1 in decimal'.}
{'company_name': 'group_B', 'total_codes': '2', total_amount:'total_amount2 in decimal'.}
{'company_name': 'group_C', 'total_codes': '3', total_amount:'total_amount3 in decimal'.}
]
"""
purchased_courses = PaidCourseRegistration.objects.filter(course_id=course_id, status='purchased').order_by('order')
purchased_course_reg_codes = CourseRegCodeItem.objects.filter(course_id=course_id, status='purchased').order_by('order')
def sale_order_info(purchased_course, features):
"""
convert purchase transactions to dictionary
"""
sale_order_features = [x for x in SALE_ORDER_FEATURES if x in features]
course_reg_features = [x for x in COURSE_REGISTRATION_FEATURES if x in features]
# Extracting order information
sale_order_dict = dict((feature, getattr(purchased_course.order, feature))
for feature in sale_order_features)
quantity = int(getattr(purchased_course, 'qty'))
unit_cost = float(getattr(purchased_course, 'unit_cost'))
sale_order_dict.update({"total_amount": quantity * unit_cost})
sale_order_dict.update({"logged_in_username": purchased_course.order.user.username})
sale_order_dict.update({"logged_in_email": purchased_course.order.user.email})
sale_order_dict.update({"total_codes": 'N/A'})
sale_order_dict.update({'total_used_codes': 'N/A'})
if getattr(purchased_course.order, 'order_type') == OrderTypes.BUSINESS:
registration_codes = CourseRegistrationCode.objects.filter(order=purchased_course.order, course_id=course_id)
sale_order_dict.update({"total_codes": registration_codes.count()})
sale_order_dict.update({'total_used_codes': RegistrationCodeRedemption.objects.filter(registration_code__in=registration_codes).count()})
codes = list()
for reg_code in registration_codes:
codes.append(reg_code.code)
# Extracting registration code information
obj_course_reg_code = registration_codes.all()[:1].get()
course_reg_dict = dict((feature, getattr(obj_course_reg_code, feature))
for feature in course_reg_features)
course_reg_dict['course_id'] = course_id.to_deprecated_string()
course_reg_dict.update({'codes': ", ".join(codes)})
sale_order_dict.update(dict(course_reg_dict.items()))
return sale_order_dict
csv_data = [sale_order_info(purchased_course, features) for purchased_course in purchased_courses]
csv_data.extend([sale_order_info(purchased_course_reg_code, features) for purchased_course_reg_code in purchased_course_reg_codes])
return csv_data
def sale_record_features(course_id, features):
"""
Return list of sales features as dictionaries.
......@@ -73,7 +142,7 @@ def purchase_transactions(course_id, features):
"""
Return list of purchased transactions features as dictionaries.
purchase_transactions(course_id, ['username, email', unit_cost])
purchase_transactions(course_id, ['username, email','created_by', unit_cost])
would return [
{'username': 'username1', 'email': 'email1', unit_cost:'cost1 in decimal'.}
{'username': 'username2', 'email': 'email2', unit_cost:'cost2 in decimal'.}
......
......@@ -7,11 +7,11 @@ from student.models import CourseEnrollment
from django.core.urlresolvers import reverse
from student.tests.factories import UserFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption, Order, Invoice, Coupon
from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption, Order, Invoice, Coupon, CourseRegCodeItem
from instructor_analytics.basic import (
sale_record_features, enrolled_students_features, course_registration_features, coupon_codes_features,
AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES
sale_record_features, sale_order_record_features, enrolled_students_features, course_registration_features,
coupon_codes_features, AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES
)
from course_groups.tests.helpers import CohortFactory
from course_groups.models import CourseUserGroup
......@@ -137,6 +137,57 @@ class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase):
self.assertEqual(sale_record['total_used_codes'], 0)
self.assertEqual(sale_record['total_codes'], 5)
def test_sale_order_features(self):
"""
Test Order Sales Report CSV
"""
query_features = [
('id', 'Order Id'),
('company_name', 'Company Name'),
('company_contact_name', 'Company Contact Name'),
('company_contact_email', 'Company Contact Email'),
('total_amount', 'Total Amount'),
('total_codes', 'Total Codes'),
('total_used_codes', 'Total Used Codes'),
('logged_in_username', 'Login Username'),
('logged_in_email', 'Login User Email'),
('purchase_time', 'Date of Sale'),
('customer_reference_number', 'Customer Reference Number'),
('recipient_name', 'Recipient Name'),
('recipient_email', 'Recipient Email'),
('bill_to_street1', 'Street 1'),
('bill_to_street2', 'Street 2'),
('bill_to_city', 'City'),
('bill_to_state', 'State'),
('bill_to_postalcode', 'Postal Code'),
('bill_to_country', 'Country'),
('order_type', 'Order Type'),
('codes', 'Registration Codes'),
('course_id', 'Course Id')
]
order = Order.get_cart_for_user(self.instructor)
order.order_type = 'business'
order.save()
order.add_billing_details(company_name='Test Company', company_contact_name='Test',
company_contact_email='test@123', recipient_name='R1',
recipient_email='', customer_reference_number='PO#23')
CourseRegCodeItem.add_to_order(order, self.course.id, 4)
order.purchase()
db_columns = [x[0] for x in query_features]
sale_order_records_list = sale_order_record_features(self.course.id, db_columns)
for sale_order_record in sale_order_records_list:
self.assertEqual(sale_order_record['recipient_email'], order.recipient_email)
self.assertEqual(sale_order_record['recipient_name'], order.recipient_name)
self.assertEqual(sale_order_record['company_name'], order.company_name)
self.assertEqual(sale_order_record['company_contact_name'], order.company_contact_name)
self.assertEqual(sale_order_record['company_contact_email'], order.company_contact_email)
self.assertEqual(sale_order_record['customer_reference_number'], order.customer_reference_number)
self.assertEqual(sale_order_record['total_used_codes'], order.registrationcoderedemption_set.all().count())
self.assertEqual(sale_order_record['total_codes'], len(CourseRegistrationCode.objects.filter(order=order)))
class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
""" Test basic course registration codes analytics functions. """
......
......@@ -21,6 +21,6 @@ def user_has_cart_context_processor(request):
settings.FEATURES.get('ENABLE_SHOPPING_CART') and # settings enable shopping cart and
shoppingcart.models.Order.user_cart_has_items(
request.user,
shoppingcart.models.PaidCourseRegistration
[shoppingcart.models.PaidCourseRegistration, shoppingcart.models.CourseRegCodeItem]
) # user's cart has PaidCourseRegistrations
)}
......@@ -19,15 +19,17 @@ from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, mixed_store_config
)
from xmodule.modulestore.tests.factories import CourseFactory
from shoppingcart.models import (
Order, OrderItem, CertificateItem,
InvalidCartItem, PaidCourseRegistration,
InvalidCartItem, CourseRegistrationCode, PaidCourseRegistration, CourseRegCodeItem,
Donation, OrderItemSubclassPK
)
from student.tests.factories import UserFactory
from student.models import CourseEnrollment
from course_modes.models import CourseMode
from shoppingcart.exceptions import PurchasedCallbackException, CourseDoesNotExistException
from shoppingcart.exceptions import (PurchasedCallbackException, CourseDoesNotExistException,
ItemAlreadyInCartException, AlreadyEnrolledInCourseException)
from opaque_keys.edx.locator import CourseLocator
......@@ -63,21 +65,21 @@ class OrderTest(ModuleStoreTestCase):
item = OrderItem(order=cart, user=self.user)
item.save()
self.assertTrue(Order.user_cart_has_items(self.user))
self.assertFalse(Order.user_cart_has_items(self.user, CertificateItem))
self.assertFalse(Order.user_cart_has_items(self.user, PaidCourseRegistration))
self.assertFalse(Order.user_cart_has_items(self.user, [CertificateItem]))
self.assertFalse(Order.user_cart_has_items(self.user, [PaidCourseRegistration]))
def test_user_cart_has_paid_course_registration_items(self):
cart = Order.get_cart_for_user(self.user)
item = PaidCourseRegistration(order=cart, user=self.user)
item.save()
self.assertTrue(Order.user_cart_has_items(self.user, PaidCourseRegistration))
self.assertFalse(Order.user_cart_has_items(self.user, CertificateItem))
self.assertTrue(Order.user_cart_has_items(self.user, [PaidCourseRegistration]))
self.assertFalse(Order.user_cart_has_items(self.user, [CertificateItem]))
def test_user_cart_has_certificate_items(self):
cart = Order.get_cart_for_user(self.user)
CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor')
self.assertTrue(Order.user_cart_has_items(self.user, CertificateItem))
self.assertFalse(Order.user_cart_has_items(self.user, PaidCourseRegistration))
self.assertTrue(Order.user_cart_has_items(self.user, [CertificateItem]))
self.assertFalse(Order.user_cart_has_items(self.user, [PaidCourseRegistration]))
def test_cart_clear(self):
cart = Order.get_cart_for_user(user=self.user)
......@@ -189,7 +191,7 @@ class OrderTest(ModuleStoreTestCase):
def test_purchase_item_email_smtp_failure(self, error_logger):
cart = Order.get_cart_for_user(user=self.user)
CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor')
with patch('shoppingcart.models.send_mail', side_effect=smtplib.SMTPException):
with patch('shoppingcart.models.EmailMessage.send', side_effect=smtplib.SMTPException):
cart.purchase()
self.assertTrue(error_logger.called)
......@@ -326,6 +328,15 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
self.assertEqual(self.cart.total_cost, self.cost)
def test_cart_type_business(self):
self.cart.order_type = 'business'
self.cart.save()
item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
self.cart.purchase()
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key))
# check that the registration codes are generated against the order
self.assertEqual(len(CourseRegistrationCode.objects.filter(order=self.cart)), item.qty)
def test_add_with_default_mode(self):
"""
Tests add_to_cart where the mode specified in the argument is NOT in the database
......@@ -341,6 +352,31 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
self.assertEqual(self.cart.total_cost, 0)
self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_key))
course_reg_code_item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2, mode_slug="DNE")
self.assertEqual(course_reg_code_item.unit_cost, 0)
self.assertEqual(course_reg_code_item.line_cost, 0)
self.assertEqual(course_reg_code_item.mode, "honor")
self.assertEqual(course_reg_code_item.user, self.user)
self.assertEqual(course_reg_code_item.status, "cart")
self.assertEqual(self.cart.total_cost, 0)
self.assertTrue(CourseRegCodeItem.contained_in_order(self.cart, self.course_key))
def test_add_course_reg_item_with_no_course_item(self):
fake_course_id = CourseLocator(org="edx", course="fake", run="course")
with self.assertRaises(CourseDoesNotExistException):
CourseRegCodeItem.add_to_order(self.cart, fake_course_id, 2)
def test_course_reg_item_already_in_cart(self):
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
with self.assertRaises(ItemAlreadyInCartException):
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
def test_course_reg_item_already_enrolled_in_course(self):
CourseEnrollment.enroll(self.user, self.course_key)
with self.assertRaises(AlreadyEnrolledInCourseException):
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
def test_purchased_callback(self):
reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
self.cart.purchase()
......@@ -382,6 +418,12 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
reg1.purchased_callback()
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key))
course_reg_code_item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
course_reg_code_item.course_id = CourseLocator(org="changed1", course="forsome1", run="reason1")
course_reg_code_item.save()
with self.assertRaises(PurchasedCallbackException):
course_reg_code_item.purchased_callback()
def test_user_cart_has_both_items(self):
"""
This test exists b/c having both CertificateItem and PaidCourseRegistration in an order used to break
......
......@@ -13,7 +13,8 @@ from django.test.utils import override_settings
from course_modes.models import CourseMode
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from shoppingcart.models import (Order, CertificateItem, PaidCourseRegistration, PaidCourseRegistrationAnnotation)
from shoppingcart.models import (Order, CertificateItem, PaidCourseRegistration, PaidCourseRegistrationAnnotation,
CourseRegCodeItemAnnotation)
from shoppingcart.views import initialize_report
from student.tests.factories import UserFactory
from student.models import CourseEnrollment
......@@ -203,6 +204,8 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
course_mode2.save()
self.annotation = PaidCourseRegistrationAnnotation(course_id=self.course_key, annotation=self.TEST_ANNOTATION)
self.annotation.save()
self.course_reg_code_annotation = CourseRegCodeItemAnnotation(course_id=self.course_key, annotation=self.TEST_ANNOTATION)
self.course_reg_code_annotation.save()
self.cart = Order.get_cart_for_user(self.user)
self.reg = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
self.cert_item = CertificateItem.add_to_order(self.cart, self.course_key, self.cost, 'verified')
......@@ -269,3 +272,9 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
Fill in gap in test coverage. __unicode__ method of PaidCourseRegistrationAnnotation
"""
self.assertEqual(unicode(self.annotation), u'{} : {}'.format(self.course_key.to_deprecated_string(), self.TEST_ANNOTATION))
def test_courseregcodeitemannotationannotation_unicode(self):
"""
Fill in gap in test coverage. __unicode__ method of CourseRegCodeItemAnnotation
"""
self.assertEqual(unicode(self.course_reg_code_annotation), u'{} : {}'.format(self.course_key.to_deprecated_string(), self.TEST_ANNOTATION))
......@@ -17,6 +17,9 @@ if settings.FEATURES['ENABLE_SHOPPING_CART']:
url(r'^add/course/{}/$'.format(settings.COURSE_ID_PATTERN), 'add_course_to_cart', name='add_course_to_cart'),
url(r'^register/redeem/(?P<registration_code>[0-9A-Za-z]+)/$', 'register_code_redemption', name='register_code_redemption'),
url(r'^use_code/$', 'use_code'),
url(r'^update_user_cart/$', 'update_user_cart'),
url(r'^reset_code_redemption/$', 'reset_code_redemption'),
url(r'^billing_details/$', 'billing_details', name='billing_details'),
url(r'^register_courses/$', 'register_courses'),
)
......
......@@ -11,6 +11,7 @@ class ECommerce
# gather elements
@$list_purchase_csv_btn = @$section.find("input[name='list-purchase-transaction-csv']'")
@$list_sale_csv_btn = @$section.find("input[name='list-sale-csv']'")
@$list_order_sale_csv_btn = @$section.find("input[name='list-order-sale-csv']'")
@$download_company_name = @$section.find("input[name='download_company_name']'")
@$active_company_name = @$section.find("input[name='active_company_name']'")
@$spent_company_name = @$section.find('input[name="spent_company_name"]')
......@@ -35,6 +36,10 @@ class ECommerce
url += '/csv'
location.href = url
@$list_order_sale_csv_btn.click (e) =>
url = @$list_order_sale_csv_btn.data 'endpoint'
location.href = url
@$download_coupon_codes.click (e) =>
url = @$download_coupon_codes.data 'endpoint'
location.href = url
......
......@@ -55,6 +55,15 @@ span {
font: inherit;
}
.text-center {
text-align: center;
}
.text-dark-grey {
color: $dark-gray1;
font-size: 24px;
}
p + p, ul + p, ol + p {
margin-top: 20px;
}
......
......@@ -421,3 +421,14 @@ $header-sans-serif: 'Open Sans', Arial, Helvetica, sans-serif;
// SPLINT: colors
$msg-bg: $action-primary-bg;
// New Shopping Cart
$dark-gray1: #4a4a4a;
$light-gray1: #f2f2f2;
$light-gray2: #ababab;
$dark-gray2: #979797;
$blue1: #4A90E2;
$blue2: #00A1E5;
$green1: #61A12E;
$red1: #D0021B;
......@@ -1144,6 +1144,9 @@ input[name="subject"] {
}
#e-commerce{
input[name='list-order-sale-csv'] {
margin-right: 14px;
}
input {
margin-bottom: 1em;
line-height: 1.3em;
......@@ -1292,22 +1295,20 @@ input[name="subject"] {
width: 650px;
margin-left: -325px;
border-radius: 2px;
input[type="submit"]#update_coupon_button{
@include button(simple, $blue);
@extend .button-reset;
}
input[type="submit"]#add_coupon_button{
input[type="button"]#update_coupon_button, input[type="button"]#add_coupon_button,
input[type="button"]#set_course_button {
@include button(simple, $blue);
@extend .button-reset;
display: block;
height: auto;
margin: 0 auto;
width: 100%;
white-space: normal;
}
input[name="generate-registration-codes-csv"]{
@include button(simple, $blue);
@extend .button-reset;
}
input[type="submit"]#set_course_button{
@include button(simple, $blue);
@extend .button-reset;
}
.modal-form-error {
box-shadow: inset 0 -1px 2px 0 #f3d9db;
-webkit-box-sizing: border-box;
......
<%! from django.utils.translation import ugettext as _ %>
<p>
${_("Hi {name},").format(name=recipient_name)}
</p>
<p>
${_("Thank you for your purchase of ")} <b> ${course_names} </b>
</p>
%if recipient_type == 'user':
<p>${_("Your payment was successful.")}</p>
% if marketing_link('FAQ'):
<p>${_("If you have billing questions, please read the FAQ ({faq_url}) or contact {billing_email}.").format(billing_email=payment_support_email, faq_url=marketing_link('FAQ'))}</p>
% else:
<p>${_("If you have billing questions, please contact {billing_email}.").format(billing_email=payment_support_email)}</p>
% endif
%elif recipient_type == 'company_contact':
<p>${_("{order_placed_by} placed an order and mentioned your name as the Organization contact.").format(order_placed_by=order_placed_by)}</p>
%elif recipient_type == 'email_recipient':
<p>${_("{order_placed_by} placed an order and mentioned your name as the additional receipt recipient.").format(order_placed_by=order_placed_by)}</p>
%endif
<p>${_("The items in your order are:")}</p>
<p>${_("Quantity - Description - Price")}<br>
%for order_item in order_items:
${order_item.qty} - ${order_item.line_desc} - ${"$" if order_item.currency == 'usd' else ""}${order_item.line_cost}</p>
%endfor
<p>${_("Total billed to credit/debit card: {currency_symbol}{total_cost}").format(total_cost=order.total_cost, currency_symbol=("$" if order.currency == 'usd' else ""))}</p>
<p>
% if order.company_name:
${_('Company Name:')} ${order.company_name}<br>
%endif
% if order.customer_reference_number:
${_('Purchase Order Number:')} ${order.customer_reference_number}<br>
%endif
% if order.company_contact_name:
${_('Company Contact Name:')} ${order.company_contact_name}<br>
%endif
% if order.company_contact_email:
${_('Company Contact Email:')} ${order.company_contact_email}<br>
%endif
% if order.recipient_name:
${_('Recipient Name:')} ${order.recipient_name}<br>
%endif
% if order.recipient_email:
${_('Recipient Email:')} ${order.recipient_email}<br>
%endif
% if has_billing_info:
${order.bill_to_cardtype} ${_("#:")} ${order.bill_to_ccnum}<br>
${order.bill_to_first} ${order.bill_to_last}<br>
${order.bill_to_street1}<br>
${order.bill_to_street2}<br>
${order.bill_to_city}, ${order.bill_to_state} ${order.bill_to_postalcode}<br>
${order.bill_to_country.upper()}
% endif
</p>
<p>${_("Order Number: {order_number}").format(order_number=order.id)}</p>
<p><b>${_("A CSV file of your registration URLs is attached. Please distribute registration URLs to each student planning to enroll using the email template below.")}</b></p>
<p>${_("Warm regards,")}<br>
% if payment_email_signature:
${payment_email_signature}
% else:
${_("The {platform_name} Team").format(platform_name=platform_name)}
%endif
</p>
———————————————————————————————————————————
<p>Dear [[Name]]</p>
<p>To enroll in ${course_names} we have provided a registration URL for you. Please follow the instructions below to claim your access.</p>
<p>Your redeem url is: [[Enter Redeem URL here from the attached CSV]]</p>
<p>${_("(1) Register for an account at <a href='https://{site_name}' >https://{site_name}</a>.").format(site_name=site_name)}<br>
${_("(2) Once registered, copy the redeem URL and paste it in your web browser.")}<br>
${_("(3) On the enrollment confirmation page, Click the 'Activate Enrollment Code' button. This will show the enrollment confirmation.")}<br>
${_("(4) You should be able to click on 'view course' button or see your course on your student dashboard at <a href='https://{dashboard_url}'>https://{dashboard_url}</a>").format(dashboard_url=dashboard_url)}<br>
${_("(5) Course materials will not be available until the course start date.")}</p>
<p>Sincerely,</p>
<p>[[Your Signature]]</p>
......@@ -4,11 +4,17 @@ ${_("Hi {name}").format(name=order.user.profile.name)}
${_("Your payment was successful. You will see the charge below on your next credit or debit card statement.")}
${_("The charge will show up on your statement under the company name {merchant_name}.").format(merchant_name=settings.CC_MERCHANT_NAME)}
% if marketing_link('FAQ'):
${_("If you have billing questions, please read the FAQ ({faq_url}) or contact {billing_email}.").format(billing_email=settings.PAYMENT_SUPPORT_EMAIL, faq_url=marketing_link('FAQ'))}
${_("If you have billing questions, please read the FAQ ({faq_url}) or contact {billing_email}.").format(billing_email=payment_support_email, faq_url=marketing_link('FAQ'))}
% else:
${_("If you have billing questions, please contact {billing_email}.").format(billing_email=settings.PAYMENT_SUPPORT_EMAIL)}
${_("If you have billing questions, please contact {billing_email}.").format(billing_email=payment_support_email)}
% endif
${_("-The {platform_name} Team").format(platform_name=settings.PLATFORM_NAME)}
${_("Warm regards,")}
% if payment_email_signature:
${payment_email_signature}
% else:
${_("The {platform_name} Team").format(platform_name=platform_name)}
%endif
${_("Your order number is: {order_number}").format(order_number=order.id)}
......@@ -16,7 +22,7 @@ ${_("The items in your order are:")}
${_("Quantity - Description - Price")}
%for order_item in order_items:
${order_item.qty} - ${order_item.line_desc} - ${"$" if order_item.currency == 'usd' else ""}${order_item.line_cost}
${order_item.qty} - ${order_item.line_desc} - ${"$" if order_item.currency == 'usd' else ""}${order_item.line_cost}
%endfor
${_("Total billed to credit/debit card: {currency_symbol}{total_cost}").format(total_cost=order.total_cost, currency_symbol=("$" if order.currency == 'usd' else ""))}
......
<%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%page args="section_data"/>
<section id="add-coupon-modal" class="modal" role="dialog" tabindex="-1" aria-label="${_('Password Reset')}">
<section id="add-coupon-modal" class="modal" role="dialog" tabindex="-1" aria-label="${_('Add Coupon')}">
<div class="inner-wrapper">
<button class="close-modal">
<i class="icon-remove"></i>
......@@ -21,7 +21,7 @@
${_("Please enter Coupon detail below")}</p>
</div>
<form id="add_coupon_form" action="${section_data['ajax_add_coupon']}" method="post" data-remote="true">
<form id="add_coupon_form">
<div id="coupon_form_error" class="modal-form-error"></div>
<fieldset class="group group-form group-form-requiredinformation">
<legend class="is-hidden">${_("Required Information")}</legend>
......@@ -54,7 +54,7 @@
</fieldset>
<div class="submit">
<input name="submit" type="submit" id="add_coupon_button" value="${_('Add Coupon')}"/>
<input name="submit" type="button" id="add_coupon_button" value="${_('Add Coupon')}"/>
</div>
</form>
</div>
......
......@@ -68,8 +68,12 @@
<div class="wrap">
<h2>${_("Sales")}</h2>
<div>
<span class="csv_tip">${_("Click to generate a CSV file for all sales records in this course")}
<input type="button" class="add blue-button" name="list-sale-csv" value="${_("Download All e-Commerce Sales")}" data-endpoint="${ section_data['get_sale_records_url'] }" data-csv="true"></p></td>
<span class="csv_tip">
<div >
${_("Click to generate a CSV file for all sales records in this course")}
<input type="button" class="add blue-button" name="list-sale-csv" value="${_("Download All Invoice Sales")}" data-endpoint="${ section_data['get_sale_records_url'] }" data-csv="true">
<input type="button" class="add blue-button" name="list-order-sale-csv" value="${_("Download All Order Sales")}" data-endpoint="${ section_data['get_sale_order_records_url'] }" data-csv="true">
</div>
</span>
<hr>
<p>${_("Enter the invoice number to invalidate or re-validate sale")}</p>
......@@ -384,8 +388,28 @@
modal_overLay.hide();
});
$('#edit_coupon_form').submit(function () {
$('#update_coupon_button').click(function () {
$("#update_coupon_button").attr('disabled', true);
var coupon_id = $.trim($('#coupon_id').val());
var description = $.trim($('#edit_coupon_description').val());
$.ajax({
type: "POST",
data: {
"coupon_id" : coupon_id,
"description": description
},
url: "${section_data['ajax_update_coupon']}",
success: function (data) {
location.reload(true);
},
error: function(jqXHR, textStatus, errorThrown) {
var data = $.parseJSON(jqXHR.responseText);
$("#update_coupon_button").removeAttr('disabled');
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#edit_coupon_form #coupon_form_error').text(data.message);
}
});
});
$('#course_price_link').click(function () {
reset_input_fields();
......@@ -397,7 +421,7 @@
reset_input_fields();
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
});
$('#set_price_form').submit(function () {
$('#set_course_button').click(function () {
$("#set_course_button").attr('disabled', true);
// Get the Code and Discount value and trim it
var course_price = $.trim($('#mode_price').val());
......@@ -422,12 +446,31 @@
$("#set_course_button").removeAttr('disabled');
return false;
}
$.ajax({
type: "POST",
data: {
"course_price" : course_price,
"currency": currency
},
url: "${section_data['set_course_mode_url']}",
success: function (data) {
location.reload(true);
},
error: function(jqXHR, textStatus, errorThrown) {
var data = $.parseJSON(jqXHR.responseText);
$("#set_course_button").removeAttr('disabled');
$('#set_price_form #course_form_error').attr('style', 'display: block !important');
$('#set_price_form #course_form_error').text(data.message);
}
});
});
$('#add_coupon_form').submit(function () {
$('#add_coupon_button').click(function () {
$("#add_coupon_button").attr('disabled', true);
// Get the Code and Discount value and trim it
var code = $.trim($('#coupon_code').val());
var coupon_discount = $.trim($('#coupon_discount').val());
var course_id = $.trim($('#coupon_course_id').val());
var description = $.trim($('#coupon_description').val());
// Check if empty of not
if (code === '') {
......@@ -448,36 +491,25 @@
$('#add_coupon_form #coupon_form_error').text("${_('Please enter the numeric value for discount')}");
return false;
}
});
$('#set_price_form').on('ajax:complete', function (event, xhr) {
if (xhr.status == 200) {
location.reload(true);
} else {
$("#set_course_button").removeAttr('disabled');
$('#set_price_form #course_form_error').attr('style', 'display: block !important');
$('#set_price_form #course_form_error').text(xhr.responseText);
}
});
$('#add_coupon_form').on('ajax:complete', function (event, xhr) {
if (xhr.status == 200) {
location.reload(true);
} else {
$("#add_coupon_button").removeAttr('disabled');
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#add_coupon_form #coupon_form_error').text(xhr.responseText);
}
});
$('#edit_coupon_form').on('ajax:complete', function (event, xhr) {
if (xhr.status == 200) {
location.reload(true);
} else {
$("#update_coupon_button").removeAttr('disabled');
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#edit_coupon_form #coupon_form_error').text(xhr.responseText);
}
$.ajax({
type: "POST",
data: {
"code" : code,
"discount": coupon_discount,
"course_id": course_id,
"description": description
},
url: "${section_data['ajax_add_coupon']}",
success: function (data) {
location.reload(true);
},
error: function(jqXHR, textStatus, errorThrown) {
var data = $.parseJSON(jqXHR.responseText);
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#add_coupon_form #coupon_form_error').text(data.message);
$("#add_coupon_button").removeAttr('disabled');
}
});
});
// removing close link's default behavior
$('.close-modal').click(function (e) {
......
......@@ -54,7 +54,7 @@
<div class="submit">
<input type="hidden" name="coupon_id" id="coupon_id"/>
<input name="submit" type="submit" id="update_coupon_button" value="${_('Update Coupon')}"/>
<input name="submit" type="button" id="update_coupon_button" value="${_('Update Coupon')}"/>
</div>
</form>
</div>
......
......@@ -21,7 +21,7 @@
${_("Please enter Course Mode detail below")}</p>
</div>
<form id="set_price_form" action="${section_data['set_course_mode_url']}" method="post" data-remote="true">
<form id="set_price_form">
<div id="course_form_error" class="modal-form-error"></div>
<fieldset class="group group-form group-form-requiredinformation">
<legend class="is-hidden">${_("Required Information")}</legend>
......@@ -40,7 +40,7 @@
</ol>
</fieldset>
<div class="submit">
<input name="submit" type="submit" id="set_course_button" value="${_('Set Price')}"/>
<input name="submit" type="button" id="set_course_button" value="${_('Set Price')}"/>
</div>
</form>
</div>
......
<%inherit file="shopping_cart_flow.html" />
<%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%block name="billing_details_highlight"><li class="active" >${_('Billing Details')}</li></%block>
<%block name="confirmation_highlight"></%block>
<%block name="custom_content">
<div class="container">
% if shoppingcart_items:
<section class="confirm-enrollment shopping-cart">
<h3>${_('You can proceed to payment at any point in time. Any additional information you provide will be included in your receipt.')}</h3>
<div class="billing-data">
<div class="col-half">
<h3>${_('Purchasing Organizational Details')}</h3>
<div class="data-group"><label for="id_company_name">${_('Purchasing organization')}</label><input type="text" id="id_company_name" name="company_name"></div>
<div class="data-group"><label for="id_customer_reference_number">${_('Purchase order number (if any)')}</label><input type="text" id="id_customer_reference_number" maxlength="63" name="customer_reference_number"></div>
</div>
<div class="col-half">
<h3>${_('Organization Contact')}</h3>
<div class="data-group"><label for="id_company_contact_name">${_('Name')}</label><input type="text"id="id_company_contact_name" name="company_contact_name"></div>
<div class="data-group"><label for="id_company_contact_email">${_('Email Address')}</label><input type="email" placeholder="${_('email@example.com')}" id="id_company_contact_email" name="company_contact_email"><span id="company_contact_email_error" class="error-text"></span></div>
</div>
<div class="col-half">
<h3>${_('Additional Receipt Recipient')}</h3>
<div class="data-group">
<label for="id_recipient_name">${_('Name')}</label>
<input type="text" id="id_recipient_name" name="recipient_name">
</div>
<div class="data-group">
<label for="id_recipient_email">${_('Email Address')}</label>
<input type="email" id="id_recipient_email" placeholder="${_('email@example.com')}" name="recipient_email">
<span id="recipient_email_error" class="error-text"></span>
</div>
</div>
</div>
<div class="discount">
<div class="code-text">
<span class="pull-right">${_('Total')}: <b>$${"{0:0.2f}".format(amount)} USD</b></span>
</div>
</div>
<div class="col-two">
<div class="col-2">
${form_html}
<p>
${_('If no additional billing details are populated the payment confirmation will be sent to the user making the purchase')}
</p>
</div>
</div>
<div class="disclaimer">${_('Payment processing occurs on a separate secure site.')}</div>
</section>
%else:
<div class="empty-cart" >
<h2>${_('Your Shopping cart is currently empty.')}</h2>
<a href="${marketing_link('COURSES')}" class="blue">${_('View Courses')}</a>
</div>
%endif
</div>
</%block>
<script>
$(function() {
function validateEmail(sEmail) {
filter = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/
return filter.test(sEmail)
}
$('form input[type="submit"]').click(function(event) {
var is_valid_email = true;
var payment_form = $(this).parent('form');
var recipient_email = $('input[name="recipient_email"]').val();
var company_contact_email = $('input[name="company_contact_email"]').val();
if ( recipient_email != '' && !(validateEmail(recipient_email))) {
$('span#recipient_email_error').html('Please enter valid email address');
$('input[name="recipient_email"]').addClass('error');
is_valid_email = false;
}
else {
$('input[name="recipient_email"]').removeClass('error');
$('span#recipient_email_error').html('');
}
if ( company_contact_email != '' && !(validateEmail(company_contact_email))) {
$('span#company_contact_email_error').html('Please enter valid email address');
$('input[name="company_contact_email"]').addClass('error');
is_valid_email = false;
}
else {
$('input[name="company_contact_email"]').removeClass('error');
$('span#company_contact_email_error').html('');
}
if (!is_valid_email) {
return false;
}
event.preventDefault();
var post_url = "${reverse('billing_details')}";
var data = {
"company_name" : $('input[name="company_name"]').val(),
"company_contact_name" : $('input[name="company_contact_name"]').val(),
"company_contact_email" : company_contact_email,
"recipient_name" : $('input[name="recipient_name"]').val(),
"recipient_email" : recipient_email,
"customer_reference_number" : $('input[name="customer_reference_number"]').val()
};
$.post(post_url, data)
.success(function(data) {
payment_form.submit();
})
.error(function(data,status) {
})
});
});
</script>
......@@ -3,5 +3,5 @@
<input type="hidden" name="${pk}" value="${pv}" />
% endfor
<input type="submit" value="Check Out" />
<i class="icon-caret-right"></i><input type="submit" value="Payment"/>
</form>
\ No newline at end of file
<%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../main.html" />
<%block name="pagetitle">${_("Your Shopping Cart")}</%block>
<section class="container cart-list">
<h2>${_("Your selected items:")}</h2>
<h3 class="cart-errors" id="cart-error">Error goes here.</h3>
% if shoppingcart_items:
<table class="cart-table">
<thead>
<tr class="cart-headings">
<th class="dsc">${_("Description")}</th>
<th class="u-pr">${_("Price")}</th>
<th class="cur">${_("Currency")}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
% for item in shoppingcart_items:
<tr class="cart-items">
<td>${item.line_desc}</td>
<td>
${"{0:0.2f}".format(item.unit_cost)}
% if item.list_price != None:
<span class="old-price"> ${"{0:0.2f}".format(item.list_price)}</span>
% endif
</td>
<td>${item.currency.upper()}</td>
<td><a data-item-id="${item.id}" class='remove_line_item' href='#'>[x]</a></td>
</tr>
% endfor
<tr class="always-gray">
<td colspan="4" valign="middle" class="cart-total" align="right">
<b>${_("Total Amount")}: <span> ${"{0:0.2f}".format(amount)} </span> </b>
</td>
</tr>
</tbody>
<tfoot>
<tr class="always-white">
<td colspan="2">
<input type="text" placeholder="Enter code here" name="cart_code" id="code">
<input type="button" value="Apply Code" id="cart-code">
</td>
<td colspan="4" align="right">
% if amount == 0:
<input type="button" value = "Register" id="register" >
% else:
${form_html}
%endif
</td>
</tr>
</tfoot>
</table>
<!-- <input id="back_input" type="submit" value="Return" /> -->
% else:
<p>${_("You have selected no items for purchase.")}</p>
% endif
</section>
<script>
$(function() {
$('a.remove_line_item').click(function(event) {
event.preventDefault();
var post_url = "${reverse('shoppingcart.views.remove_item')}";
$.post(post_url, {id:$(this).data('item-id')})
.always(function(data){
location.reload(true);
});
});
$('#cart-code').click(function(event){
event.preventDefault();
var post_url = "${reverse('shoppingcart.views.use_code')}";
$.post(post_url,{
"code" : $('#code').val(),
beforeSend: function(xhr, options){
if($('#code').val() == "") {
showErrorMsgs('Must enter a valid code')
xhr.abort();
}
}
}
)
.success(function(data) {
location.reload(true);
})
.error(function(data,status) {
if(status=="parsererror"){
location.reload(true);
}else{
showErrorMsgs(data.responseText)
}
})
});
$('#register').click(function(event){
event.preventDefault();
var post_url = "${reverse('shoppingcart.views.register_courses')}";
$.post(post_url)
.success(function(data) {
window.location.href = "${reverse('dashboard')}";
})
.error(function(data,status) {
if(status=="parsererror"){
location.reload(true);
}else{
showErrorMsgs(data.responseText)
}
})
});
$('#back_input').click(function(){
history.back();
});
function showErrorMsgs(msg){
$(".cart-errors").css('display', 'block');
$("#cart-error").html(msg);
}
});
</script>
\ No newline at end of file
<%!
from django.utils.translation import ugettext as _
%>
<%inherit file="../main.html" />
<%namespace name='static' file='/static_content.html'/>
<%block name="pagetitle">${_("Shopping cart")}</%block>
<%block name="bodyextra">
<div class="container">
<section class="wrapper confirm-enrollment shopping-cart">
<h1> ${_("{site_name} - Shopping Cart").format(site_name=site_name)}</h1>
% if shoppingcart_items:
<ul class="steps">
<li <%block name="review_highlight"/>>${_('Review')}</li>
<%block name="billing_details_highlight"/>
<li <%block name="payment_highlight"/>>${_('Payment')}</li>
<li <%block name="confirmation_highlight"/>>${_('Confirmation')}</li>
</ul>
%endif
</section>
</div>
<%block name="custom_content"/>
</%block>
......@@ -103,7 +103,7 @@
</thead>
<tbody>
% for item in order_items:
% for item, course in shoppingcart_items:
<tr>
<td>${item.line_desc}</td>
<td>
......@@ -158,7 +158,7 @@
</thead>
<tbody>
% for item in order_items:
% for item, course in shoppingcart_items:
<tr>
% if item.status == "purchased":
<td>${order.id}</td>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment