Merge pull request #6071 from edx/muhhshoaib/WL-135-add-expiration-dates-to-Coupon-codes
Add expiration dates to Coupon Codes
This commit is contained in:
@@ -4,6 +4,8 @@ Unit tests for instructor.api methods.
|
||||
"""
|
||||
import datetime
|
||||
import ddt
|
||||
import random
|
||||
import pytz
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
@@ -61,7 +63,7 @@ from .test_tools import msk_from_problem_urlname
|
||||
from ..views.tools import get_extended_due
|
||||
|
||||
EXPECTED_CSV_HEADER = '"code","course_id","company_name","created_by","redeemed_by","invoice_id","purchaser","customer_reference_number","internal_reference"'
|
||||
EXPECTED_COUPON_CSV_HEADER = '"course_id","percentage_discount","code_redeemed_count","description"'
|
||||
EXPECTED_COUPON_CSV_HEADER = '"code","course_id","percentage_discount","code_redeemed_count","description"'
|
||||
|
||||
# ddt data for test cases involving reports
|
||||
REPORTS_DATA = (
|
||||
@@ -3331,8 +3333,27 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
#now create coupons with the expiration dates
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
response = self.client.get(get_coupon_code_url)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
# filter all the coupons
|
||||
for coupon in Coupon.objects.all():
|
||||
self.assertIn('"{code}","{course_id}","{discount}","0","{description}","{expiration_date}"'.format(
|
||||
code=coupon.code,
|
||||
course_id=coupon.course_id,
|
||||
discount=coupon.percentage_discount,
|
||||
description=coupon.description,
|
||||
expiration_date=coupon.display_expiry_date
|
||||
), response.content)
|
||||
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_COUPON_CSV_HEADER))
|
||||
|
||||
@@ -3,6 +3,8 @@ Unit tests for Ecommerce feature flag in new instructor dashboard.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
import datetime
|
||||
import pytz
|
||||
from django.test.utils import override_settings
|
||||
from mock import patch
|
||||
|
||||
@@ -144,13 +146,26 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
|
||||
"""
|
||||
# URL for add_coupon
|
||||
add_coupon_url = reverse('add_coupon', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
expiration_date = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
|
||||
data = {
|
||||
'code': 'A2314', 'course_id': self.course.id.to_deprecated_string(),
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5,
|
||||
'expiration_date': '{month}/{day}/{year}'.format(month=expiration_date.month, day=expiration_date.day, year=expiration_date.year)
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertTrue("coupon with the coupon code ({code}) added successfully".format(code=data['code']) in response.content)
|
||||
|
||||
#now add the coupon with the wrong value in the expiration_date
|
||||
# server will through the ValueError Exception in the expiration_date field
|
||||
data = {
|
||||
'code': '213454', 'course_id': self.course.id.to_deprecated_string(),
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5,
|
||||
'expiration_date': expiration_date.strftime('"%d/%m/%Y')
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertTrue("Please enter the date in this format i-e month/day/year" in response.content)
|
||||
|
||||
data = {
|
||||
'code': 'A2314', 'course_id': self.course.id.to_deprecated_string(),
|
||||
'description': 'asdsasda', 'created_by': self.instructor, 'discount': 99
|
||||
@@ -221,13 +236,15 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
|
||||
"""
|
||||
coupon = Coupon(
|
||||
code='AS452', description='asdsadsa', course_id=self.course.id.to_deprecated_string(),
|
||||
percentage_discount=10, created_by=self.instructor
|
||||
percentage_discount=10, created_by=self.instructor,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
# URL for edit_coupon_info
|
||||
edit_url = reverse('get_coupon_info', kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
response = self.client.post(edit_url, {'id': coupon.id})
|
||||
self.assertTrue('coupon with the coupon id ({coupon_id}) updated successfully'.format(coupon_id=coupon.id) in response.content)
|
||||
self.assertIn(coupon.display_expiry_date, response.content)
|
||||
|
||||
response = self.client.post(edit_url, {'id': 444444})
|
||||
self.assertTrue('coupon with the coupon id ({coupon_id}) DoesNotExist'.format(coupon_id=444444) in response.content)
|
||||
|
||||
@@ -18,6 +18,7 @@ from django.views.decorators.cache import cache_control
|
||||
from django.core.exceptions import ValidationError, PermissionDenied
|
||||
from django.core.mail.message import EmailMessage
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import validate_email
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -28,6 +29,8 @@ import random
|
||||
import unicodecsv
|
||||
import urllib
|
||||
from util.file import store_uploaded_file, course_and_time_based_filename_generator, FileValidationException, UniversalNewlineIterator
|
||||
import datetime
|
||||
import pytz
|
||||
from util.json_request import JsonResponse
|
||||
from instructor.views.instructor_task_helpers import extract_email_features, extract_task_features
|
||||
|
||||
@@ -1007,9 +1010,14 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
|
||||
Respond with csv which contains a summary of all Active Coupons.
|
||||
"""
|
||||
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
active_coupons = Coupon.objects.filter(course_id=course_id, is_active=True)
|
||||
active_coupons = Coupon.objects.filter(
|
||||
Q(course_id=course_id),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
query_features = [
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description'
|
||||
'code', 'course_id', 'percentage_discount', 'code_redeemed_count', 'description', 'expiration_date'
|
||||
]
|
||||
coupons_list = instructor_analytics.basic.coupon_codes_features(query_features, active_coupons)
|
||||
header, data_rows = instructor_analytics.csvs.format_dictlist(coupons_list, query_features)
|
||||
|
||||
@@ -10,7 +10,8 @@ from util.json_request import JsonResponse
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from shoppingcart.models import Coupon, CourseRegistrationCode
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -79,9 +80,22 @@ def add_coupon(request, course_id): # pylint: disable=unused-argument
|
||||
return JsonResponse({
|
||||
'message': _("Please Enter the Coupon Discount Value Less than or Equal to 100")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
expiration_date = None
|
||||
if request.POST.get('expiration_date'):
|
||||
expiration_date = request.POST.get('expiration_date')
|
||||
try:
|
||||
expiration_date = datetime.datetime.strptime(expiration_date, "%m/%d/%Y").replace(tzinfo=pytz.UTC) + datetime.timedelta(days=1)
|
||||
except ValueError:
|
||||
return JsonResponse({
|
||||
'message': _("Please enter the date in this format i-e month/day/year")
|
||||
}, 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
|
||||
code=code, description=description,
|
||||
course_id=course_id,
|
||||
percentage_discount=discount,
|
||||
created_by_id=request.user.id,
|
||||
expiration_date=expiration_date
|
||||
)
|
||||
coupon.save()
|
||||
return JsonResponse(
|
||||
@@ -143,10 +157,12 @@ def get_coupon_info(request, course_id): # pylint: disable=unused-argument
|
||||
'message': _("coupon with the coupon id ({coupon_id}) is already inactive").format(coupon_id=coupon_id)
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
expiry_date = coupon.display_expiry_date
|
||||
return JsonResponse({
|
||||
'coupon_code': coupon.code,
|
||||
'coupon_description': coupon.description,
|
||||
'coupon_course_id': coupon.course_id.to_deprecated_string(),
|
||||
'coupon_discount': coupon.percentage_discount,
|
||||
'expiry_date': expiry_date,
|
||||
'message': _('coupon with the coupon id ({coupon_id}) updated successfully').format(coupon_id=coupon_id)
|
||||
}) # status code 200: OK by default
|
||||
|
||||
@@ -30,7 +30,7 @@ SALE_ORDER_FEATURES = ('id', 'company_name', 'company_contact_name', 'company_co
|
||||
|
||||
AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
|
||||
COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at')
|
||||
COUPON_FEATURES = ('course_id', 'percentage_discount', 'description')
|
||||
COUPON_FEATURES = ('code', 'course_id', 'percentage_discount', 'description', 'expiration_date')
|
||||
|
||||
|
||||
def sale_order_record_features(course_id, features):
|
||||
@@ -228,6 +228,7 @@ def coupon_codes_features(features, coupons_list):
|
||||
# codes csv. In the case of active and generated registration codes the redeemed_by value will be None.
|
||||
# They have not been redeemed yet
|
||||
|
||||
coupon_dict['expiration_date'] = coupon.display_expiry_date
|
||||
coupon_dict['course_id'] = coupon_dict['course_id'].to_deprecated_string()
|
||||
return coupon_dict
|
||||
return [extract_coupon(coupon, features) for coupon in coupons_list]
|
||||
|
||||
@@ -22,6 +22,10 @@ from courseware.tests.factories import InstructorFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
import datetime
|
||||
from django.db.models import Q
|
||||
import pytz
|
||||
|
||||
|
||||
class TestAnalyticsBasic(ModuleStoreTestCase):
|
||||
""" Test basic analytics functions. """
|
||||
@@ -303,7 +307,7 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
|
||||
|
||||
def test_coupon_codes_features(self):
|
||||
query_features = [
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description'
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description', 'expiration_date'
|
||||
]
|
||||
for i in range(10):
|
||||
coupon = Coupon(
|
||||
@@ -314,13 +318,29 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
|
||||
is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
active_coupons = Coupon.objects.filter(course_id=self.course.id, is_active=True)
|
||||
#now create coupons with the expiration dates
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
active_coupons = Coupon.objects.filter(
|
||||
Q(course_id=self.course.id),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
active_coupons_list = coupon_codes_features(query_features, active_coupons)
|
||||
self.assertEqual(len(active_coupons_list), len(active_coupons))
|
||||
for active_coupon in active_coupons_list:
|
||||
self.assertEqual(set(active_coupon.keys()), set(query_features))
|
||||
self.assertIn(active_coupon['percentage_discount'], [coupon.percentage_discount for coupon in active_coupons])
|
||||
self.assertIn(active_coupon['description'], [coupon.description for coupon in active_coupons])
|
||||
if active_coupon['expiration_date']:
|
||||
self.assertIn(active_coupon['expiration_date'], [coupon.display_expiry_date for coupon in active_coupons])
|
||||
self.assertIn(
|
||||
active_coupon['course_id'],
|
||||
[coupon.course_id.to_deprecated_string() for coupon in active_coupons]
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'Coupon.expiration_date'
|
||||
db.add_column('shoppingcart_coupon', 'expiration_date',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Coupon.expiration_date'
|
||||
db.delete_column('shoppingcart_coupon', 'expiration_date')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'shoppingcart.certificateitem': {
|
||||
'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.coupon': {
|
||||
'Meta': {'object_name': 'Coupon'},
|
||||
'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 6, 0, 0)'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'expiration_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'})
|
||||
},
|
||||
'shoppingcart.couponredemption': {
|
||||
'Meta': {'object_name': 'CouponRedemption'},
|
||||
'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.courseregcodeitem': {
|
||||
'Meta': {'object_name': 'CourseRegCodeItem', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.courseregcodeitemannotation': {
|
||||
'Meta': {'object_name': 'CourseRegCodeItemAnnotation'},
|
||||
'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.courseregistrationcode': {
|
||||
'Meta': {'object_name': 'CourseRegistrationCode'},
|
||||
'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 6, 0, 0)'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'purchase_order'", 'null': 'True', 'to': "orm['shoppingcart.Order']"})
|
||||
},
|
||||
'shoppingcart.donation': {
|
||||
'Meta': {'object_name': 'Donation', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'donation_type': ('django.db.models.fields.CharField', [], {'default': "'general'", 'max_length': '32'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.donationconfiguration': {
|
||||
'Meta': {'object_name': 'DonationConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.invoice': {
|
||||
'Meta': {'object_name': 'Invoice'},
|
||||
'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'total_amount': ('django.db.models.fields.FloatField', [], {}),
|
||||
'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'})
|
||||
},
|
||||
'shoppingcart.order': {
|
||||
'Meta': {'object_name': 'Order'},
|
||||
'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
|
||||
'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
|
||||
'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}),
|
||||
'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order_type': ('django.db.models.fields.CharField', [], {'default': "'personal'", 'max_length': '32'}),
|
||||
'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.orderitem': {
|
||||
'Meta': {'object_name': 'OrderItem'},
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}),
|
||||
'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}),
|
||||
'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}),
|
||||
'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}),
|
||||
'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'shoppingcart.paidcourseregistration': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.paidcourseregistrationannotation': {
|
||||
'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'},
|
||||
'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.registrationcoderedemption': {
|
||||
'Meta': {'object_name': 'RegistrationCodeRedemption'},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']", 'null': 'True'}),
|
||||
'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 1, 6, 0, 0)', 'null': 'True'}),
|
||||
'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['shoppingcart']
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
import analytics
|
||||
import pytz
|
||||
@@ -803,12 +804,20 @@ class Coupon(models.Model):
|
||||
created_by = models.ForeignKey(User)
|
||||
created_at = models.DateTimeField(default=datetime.now(pytz.utc))
|
||||
is_active = models.BooleanField(default=True)
|
||||
expiration_date = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return "[Coupon] code: {} course: {}".format(self.code, self.course_id)
|
||||
|
||||
objects = SoftDeleteCouponManager()
|
||||
|
||||
@property
|
||||
def display_expiry_date(self):
|
||||
"""
|
||||
return the coupon expiration date in the readable format
|
||||
"""
|
||||
return (self.expiration_date - timedelta(days=1)).strftime("%B %d, %Y") if self.expiration_date else None
|
||||
|
||||
|
||||
class CouponRedemption(models.Model):
|
||||
"""
|
||||
|
||||
@@ -369,7 +369,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code})
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
self.assertIn("Coupon '{0}' is not valid for any course in the shopping cart.".format(self.coupon_code), resp.content)
|
||||
self.assertIn("Discount does not exist against code '{0}'.".format(self.coupon_code), resp.content)
|
||||
|
||||
def test_course_does_not_exist_in_cart_against_valid_reg_code(self):
|
||||
course_key = self.course_key.to_deprecated_string() + 'testing'
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import datetime
|
||||
import decimal
|
||||
import pytz
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group
|
||||
from django.http import (
|
||||
@@ -258,7 +259,12 @@ def use_code(request):
|
||||
Registration Code Redemption page.
|
||||
"""
|
||||
code = request.POST["code"]
|
||||
coupons = Coupon.objects.filter(code=code, is_active=True)
|
||||
coupons = Coupon.objects.filter(
|
||||
Q(code=code),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
if not coupons:
|
||||
# If no coupons then we check that code against course registration code
|
||||
try:
|
||||
@@ -423,9 +429,8 @@ def use_coupon_code(coupons, user):
|
||||
return HttpResponseBadRequest(_("Only one coupon redemption is allowed against an order"))
|
||||
|
||||
if not is_redemption_applied:
|
||||
log.warning("Course item does not exist for coupon '{code}'".format(code=coupons[0].code))
|
||||
return HttpResponseNotFound(
|
||||
_("Coupon '{code}' is not valid for any course in the shopping cart.".format(code=coupons[0].code)))
|
||||
log.warning("Discount does not exist against code '{code}'.".format(code=coupons[0].code))
|
||||
return HttpResponseNotFound(_("Discount does not exist against code '{code}'.".format(code=coupons[0].code)))
|
||||
|
||||
return HttpResponse(
|
||||
json.dumps({'response': 'success', 'coupon_code_applied': True}),
|
||||
|
||||
35
lms/static/js/instructor_dashboard/ecommerce.js
Normal file
35
lms/static/js/instructor_dashboard/ecommerce.js
Normal file
@@ -0,0 +1,35 @@
|
||||
var edx = edx || {};
|
||||
|
||||
(function(Backbone, $, _) {
|
||||
'use strict';
|
||||
|
||||
edx.instructor_dashboard = edx.instructor_dashboard || {};
|
||||
edx.instructor_dashboard.ecommerce = {};
|
||||
|
||||
edx.instructor_dashboard.ecommerce.ExpiryCouponView = Backbone.View.extend({
|
||||
el: 'li#add-coupon-modal-field-expiry',
|
||||
events: {
|
||||
'click input[type="checkbox"]': 'clicked'
|
||||
},
|
||||
initialize: function() {
|
||||
$('li#add-coupon-modal-field-expiry input[name="expiration_date"]').hide();
|
||||
_.bindAll(this, 'clicked');
|
||||
},
|
||||
clicked: function (event) {
|
||||
if (event.currentTarget.checked) {
|
||||
this.$el.find('#coupon_expiration_date').show();
|
||||
this.$el.find('#coupon_expiration_date').focus();
|
||||
}
|
||||
else {
|
||||
this.$el.find('#coupon_expiration_date').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$( "#coupon_expiration_date" ).datepicker({
|
||||
minDate: 0
|
||||
});
|
||||
var view = new edx.instructor_dashboard.ecommerce.ExpiryCouponView();
|
||||
});
|
||||
}).call(this, Backbone, $, _);
|
||||
36
lms/static/js/spec/instructor_dashboard/ecommerce_spec.js
Normal file
36
lms/static/js/spec/instructor_dashboard/ecommerce_spec.js
Normal file
@@ -0,0 +1,36 @@
|
||||
define(['backbone', 'jquery', 'js/instructor_dashboard/ecommerce', 'js/common_helpers/template_helpers'],
|
||||
function (Backbone, $, ExpiryCouponView, TemplateHelpers) {
|
||||
'use strict';
|
||||
var expiryCouponView, createExpiryCoupon;
|
||||
describe("edx.instructor_dashboard.ecommerce.ExpiryCouponView", function() {
|
||||
beforeEach(function() {
|
||||
setFixtures('<li class="field full-width" id="add-coupon-modal-field-expiry"><input id="expiry-check" type="checkbox"/><label for="expiry-check"></label><input type="text" id="coupon_expiration_date" class="field" name="expiration_date" aria-required="true"/></li>')
|
||||
expiryCouponView = new ExpiryCouponView();
|
||||
});
|
||||
|
||||
it("is defined", function () {
|
||||
expect(expiryCouponView).toBeDefined();
|
||||
});
|
||||
|
||||
it("triggers the callback when the checkbox is clicked", function () {
|
||||
var target = expiryCouponView.$el.find('input[type="checkbox"]');
|
||||
spyOn(expiryCouponView, 'clicked');
|
||||
expiryCouponView.delegateEvents();
|
||||
target.click();
|
||||
expect(expiryCouponView.clicked).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("shows the input field when the checkbox is checked", function () {
|
||||
var target = expiryCouponView.$el.find('input[type="checkbox"]');
|
||||
target.attr("checked","checked");
|
||||
target.click();
|
||||
expect(expiryCouponView.$el.find('#coupon_expiration_date')).toHaveAttr('style','display: inline;');
|
||||
});
|
||||
|
||||
it("hides the input field when the checkbox is unchecked", function () {
|
||||
var target = expiryCouponView.$el.find('input[type="checkbox"]');
|
||||
expect(expiryCouponView.$el.find('#coupon_expiration_date')).toHaveAttr('style','display: none;');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -270,6 +270,10 @@
|
||||
},
|
||||
|
||||
// Backbone classes loaded explicitly until they are converted to use RequireJS
|
||||
'js/instructor_dashboard/ecommerce': {
|
||||
exports: 'edx.instructor_dashboard.ecommerce.ExpiryCouponView',
|
||||
deps: ['backbone', 'jquery', 'underscore']
|
||||
},
|
||||
'js/models/cohort': {
|
||||
exports: 'CohortModel',
|
||||
deps: ['backbone']
|
||||
@@ -497,6 +501,7 @@
|
||||
'lms/include/js/spec/views/file_uploader_spec.js',
|
||||
'lms/include/js/spec/dashboard/donation.js',
|
||||
'lms/include/js/spec/shoppingcart/shoppingcart_spec.js',
|
||||
'lms/include/js/spec/instructor_dashboard/ecommerce_spec.js',
|
||||
'lms/include/js/spec/student_account/account_spec.js',
|
||||
'lms/include/js/spec/student_account/access_spec.js',
|
||||
'lms/include/js/spec/student_account/login_spec.js',
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
#ui-datepicker-div{z-index: 12000 !important; width: 16.5em !important}
|
||||
|
||||
|
||||
.instructor-dashboard-wrapper-2 {
|
||||
position: relative;
|
||||
// display: table;
|
||||
@@ -1325,12 +1328,13 @@ input[name="subject"] {
|
||||
th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid $border-color-1;
|
||||
font-size: 16px;
|
||||
|
||||
&.c_code {
|
||||
width: 170px;
|
||||
width: 110px;
|
||||
}
|
||||
&.c_count {
|
||||
width: 85px;
|
||||
width: 60px;
|
||||
}
|
||||
&.c_course_id {
|
||||
width: 320px;
|
||||
@@ -1339,11 +1343,14 @@ input[name="subject"] {
|
||||
&.c_discount {
|
||||
width: 90px;
|
||||
}
|
||||
&.c_expiry {
|
||||
width: 150px;
|
||||
}
|
||||
&.c_action {
|
||||
width: 89px;
|
||||
width: 60px;
|
||||
}
|
||||
&.c_dsc{
|
||||
width: 260px;
|
||||
width: 200px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
@@ -1361,6 +1368,16 @@ input[name="subject"] {
|
||||
}
|
||||
}
|
||||
}
|
||||
// in_active coupon rows style
|
||||
.expired_coupon{
|
||||
background: #FEEFB3 !important;
|
||||
|
||||
color: rgba(51,51,51,0.2);
|
||||
border-bottom: 1px solid #fff;
|
||||
td:nth-child(3) {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
// coupon items style
|
||||
.coupons-items {
|
||||
@@ -1368,6 +1385,7 @@ input[name="subject"] {
|
||||
padding: ($baseline/2) 0;
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
font-size: 14px;
|
||||
span.old-price{
|
||||
left: -75px;
|
||||
position: relative;
|
||||
@@ -1613,7 +1631,15 @@ input[name="subject"] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#add-coupon-modal{
|
||||
ol.list-input{
|
||||
li{
|
||||
input[type="checkbox"]#expiry-check , input[type="checkbox"]#expiry-check + label {display: inline-block; width: auto;margin-top: 10px;}
|
||||
&.full-width{width: 100%;}
|
||||
input#coupon_expiration_date{width: 278px;display: inline-block;float: right;}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile-distribution-widget {
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
<input class="field readonly" id="coupon_course_id" type="text" name="course_id" value="${section_data['course_id'] | h}"
|
||||
readonly aria-required="true"/>
|
||||
</li>
|
||||
<li class="field full-width" id="add-coupon-modal-field-expiry">
|
||||
<input id="expiry-check" type="checkbox" value="true" />
|
||||
<label for="expiry-check">${_('Add expiration date')}</label>
|
||||
<input type="text" id="coupon_expiration_date" value="" class="field" name="expiration_date"
|
||||
aria-required="true"/>
|
||||
</li>
|
||||
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from datetime import datetime, timedelta %>
|
||||
<%! import pytz %>
|
||||
<%page args="section_data"/>
|
||||
<%include file="add_coupon_modal.html" args="section_data=section_data" />
|
||||
<%include file="edit_coupon_modal.html" args="section_data=section_data" />
|
||||
@@ -95,6 +97,7 @@
|
||||
<tr class="coupons-headings">
|
||||
<th class="c_code">${_("Code")}</th>
|
||||
<th class="c_dsc">${_("Description")}</th>
|
||||
<th class="c_expiry">${_("Expiry Date")}</th>
|
||||
<th class="c_discount">${_("Discount (%)")}</th>
|
||||
<th class="c_count">${_("Redeem Count")}</th>
|
||||
<th class="c_action">${_("Actions")}</th>
|
||||
@@ -102,14 +105,21 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
%for coupon in section_data['coupons']:
|
||||
<% current_date = datetime.now(pytz.UTC) %>
|
||||
<% coupon_expiry_date = coupon.expiration_date %>
|
||||
%if coupon.is_active == False:
|
||||
<tr class="coupons-items inactive_coupon">
|
||||
%else:
|
||||
%elif coupon_expiry_date is not None and current_date >= coupon_expiry_date:
|
||||
<tr class="coupons-items expired_coupon">
|
||||
%else:
|
||||
<tr class="coupons-items">
|
||||
%endif
|
||||
<td>${coupon.code}</td>
|
||||
<td>${coupon.description}</td>
|
||||
<td>${coupon.percentage_discount}</td>
|
||||
<td>${_('{code}').format(code=coupon.code)}</td>
|
||||
<td>${_('{description}').format(description=coupon.description)}</td>
|
||||
<td>
|
||||
${coupon.display_expiry_date}
|
||||
</td>
|
||||
<td>${_('{discount}').format(discount=coupon.percentage_discount)}</td>
|
||||
<td>${ coupon.couponredemption_set.filter(order__status='purchased').count() }</td>
|
||||
<td><a data-item-id="${coupon.id}" class='remove_coupon' href='#'>[x]</a><a href="#edit-modal" data-item-id="${coupon.id}" class="edit-right">${_('Edit')}</a></td>
|
||||
</tr>
|
||||
@@ -184,7 +194,7 @@
|
||||
}
|
||||
if($('#invoice_number').val() == "") {
|
||||
$('#error-msg').attr('class','error-msgs')
|
||||
$('#error-msg').html("${_("Invoice number should not be empty.")}").show();
|
||||
$('#error-msg').html("${_('Invoice number should not be empty.')}").show();
|
||||
return
|
||||
}
|
||||
$.ajax({
|
||||
@@ -224,6 +234,12 @@
|
||||
$('input#edit_coupon_discount').val(data.coupon_discount);
|
||||
$('textarea#edit_coupon_description').val(data.coupon_description);
|
||||
$('input#edit_coupon_course_id').val(data.coupon_course_id);
|
||||
if (data.expiry_date) {
|
||||
$('input#edit_coupon_expiration_date').val(data.expiry_date);
|
||||
}
|
||||
else {
|
||||
$('input#edit_coupon_expiration_date').val("${_('Never Expires')}");
|
||||
}
|
||||
$('#edit-modal-trigger').click();
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
@@ -459,6 +475,7 @@
|
||||
var coupon_discount = $.trim($('#coupon_discount').val());
|
||||
var course_id = $.trim($('#coupon_course_id').val());
|
||||
var description = $.trim($('#coupon_description').val());
|
||||
var expiration_date = $.trim($('#coupon_expiration_date').val());
|
||||
|
||||
// Check if empty of not
|
||||
if (code === '') {
|
||||
@@ -485,7 +502,8 @@
|
||||
"code" : code,
|
||||
"discount": coupon_discount,
|
||||
"course_id": course_id,
|
||||
"description": description
|
||||
"description": description,
|
||||
"expiration_date": expiration_date
|
||||
},
|
||||
url: "${section_data['ajax_add_coupon']}",
|
||||
success: function (data) {
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
<input class="field readonly" id="edit_coupon_course_id" type="text" name="course_id" value=""
|
||||
readonly aria-required="true"/>
|
||||
</li>
|
||||
|
||||
<li class="field" id="edit-coupon-modal-field-expiration-date">
|
||||
<label for="edit_coupon_expiration_date">${_("Expiration Date")}</label>
|
||||
<input class="field readonly" id="edit_coupon_expiration_date" type="text" name="expiration_date" value=""
|
||||
readonly aria-required="true"/>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
<%static:js group='application'/>
|
||||
|
||||
## Backbone classes declared explicitly until RequireJS is supported
|
||||
<script type="text/javascript" src="${static.url('js/instructor_dashboard/ecommerce.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/models/notification.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/views/notification.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/views/file_uploader.js')}"></script>
|
||||
|
||||
Reference in New Issue
Block a user