Commit 1abf064d by chrisndodge

Merge pull request #5235 from edx/cdodge/shopping-cart-rewrite

Cdodge/shopping cart rewrite
parents be19c1a9 4f7c4949
......@@ -446,6 +446,10 @@ 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:
# 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
......
......@@ -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,11 +277,21 @@ 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())
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
......@@ -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)}
......
<%! 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) {
$.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);
} else {
$("#add_coupon_button").removeAttr('disabled');
},
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(xhr.responseText);
$('#add_coupon_form #coupon_form_error').text(data.message);
$("#add_coupon_button").removeAttr('disabled');
}
});
$('#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);
}
});
// 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