Merge pull request #23975 from edx/depr/shoppingcart-instructor-report
instructor_task: remove shoppingcart (DEPR-43)
This commit is contained in:
@@ -144,17 +144,6 @@ REPORTS_DATA = (
|
||||
}
|
||||
)
|
||||
|
||||
# ddt data for test cases involving executive summary report
|
||||
EXECUTIVE_SUMMARY_DATA = (
|
||||
{
|
||||
'report_type': 'executive summary',
|
||||
'task_type': 'exec_summary_report',
|
||||
'instructor_api_endpoint': 'get_exec_summary_report',
|
||||
'task_api_endpoint': 'lms.djangoapps.instructor_task.api.submit_executive_summary_report',
|
||||
'extra_instructor_api_kwargs': {}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
INSTRUCTOR_GET_ENDPOINTS = set([
|
||||
'get_anon_ids',
|
||||
@@ -167,7 +156,6 @@ INSTRUCTOR_POST_ENDPOINTS = set([
|
||||
'change_due_date',
|
||||
'export_ora2_data',
|
||||
'get_enrollment_report',
|
||||
'get_exec_summary_report',
|
||||
'get_grading_config',
|
||||
'get_problem_responses',
|
||||
'get_proctored_exam_results',
|
||||
@@ -445,7 +433,6 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest
|
||||
('get_students_features', {}),
|
||||
('get_enrollment_report', {}),
|
||||
('get_students_who_may_enroll', {}),
|
||||
('get_exec_summary_report', {}),
|
||||
('get_proctored_exam_results', {}),
|
||||
('get_problem_responses', {}),
|
||||
('export_ora2_data', {}),
|
||||
@@ -2875,50 +2862,6 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
|
||||
response = self.client.post(url, {})
|
||||
self.assertContains(response, success_status)
|
||||
|
||||
@ddt.data(*EXECUTIVE_SUMMARY_DATA)
|
||||
@ddt.unpack
|
||||
def test_executive_summary_report_success(
|
||||
self,
|
||||
report_type,
|
||||
task_type,
|
||||
instructor_api_endpoint,
|
||||
task_api_endpoint,
|
||||
extra_instructor_api_kwargs
|
||||
): # pylint: disable=unused-argument
|
||||
kwargs = {'course_id': text_type(self.course.id)}
|
||||
kwargs.update(extra_instructor_api_kwargs)
|
||||
url = reverse(instructor_api_endpoint, kwargs=kwargs)
|
||||
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
with patch(task_api_endpoint):
|
||||
response = self.client.post(url, {})
|
||||
success_status = u"The {report_type} report is being created." \
|
||||
" To view the status of the report, see Pending" \
|
||||
" Tasks below".format(report_type=report_type)
|
||||
self.assertContains(response, success_status)
|
||||
|
||||
@ddt.data(*EXECUTIVE_SUMMARY_DATA)
|
||||
@ddt.unpack
|
||||
def test_executive_summary_report_already_running(
|
||||
self,
|
||||
report_type,
|
||||
task_type,
|
||||
instructor_api_endpoint,
|
||||
task_api_endpoint,
|
||||
extra_instructor_api_kwargs
|
||||
):
|
||||
kwargs = {'course_id': text_type(self.course.id)}
|
||||
kwargs.update(extra_instructor_api_kwargs)
|
||||
url = reverse(instructor_api_endpoint, kwargs=kwargs)
|
||||
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
already_running_status = generate_already_running_error_message(task_type)
|
||||
with patch(task_api_endpoint) as mock:
|
||||
mock.side_effect = AlreadyRunningError(already_running_status)
|
||||
response = self.client.post(url, {})
|
||||
|
||||
self.assertContains(response, already_running_status, status_code=400)
|
||||
|
||||
def test_get_ora2_responses_success(self):
|
||||
url = reverse('export_ora2_data', kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from six import StringIO, text_type
|
||||
from six import text_type
|
||||
from six.moves import map, range
|
||||
from submissions import api as sub_api # installed from the edx-submissions repository
|
||||
|
||||
@@ -1362,25 +1362,6 @@ def get_enrollment_report(request, course_id):
|
||||
return JsonResponse({"status": success_status})
|
||||
|
||||
|
||||
@transaction.non_atomic_requests
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.ENROLLMENT_REPORT)
|
||||
@require_finance_admin
|
||||
@common_exceptions_400
|
||||
def get_exec_summary_report(request, course_id):
|
||||
"""
|
||||
get the executive summary report for the particular course.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
report_type = _('executive summary')
|
||||
task_api.submit_executive_summary_report(request, course_key)
|
||||
success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type)
|
||||
|
||||
return JsonResponse({"status": success_status})
|
||||
|
||||
|
||||
@transaction.non_atomic_requests
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
|
||||
@@ -52,7 +52,6 @@ urlpatterns = [
|
||||
|
||||
# Reports..
|
||||
url(r'^get_enrollment_report$', api.get_enrollment_report, name='get_enrollment_report'),
|
||||
url(r'^get_exec_summary_report$', api.get_exec_summary_report, name='get_exec_summary_report'),
|
||||
url(r'^get_course_survey_results$', api.get_course_survey_results, name='get_course_survey_results'),
|
||||
url(r'^export_ora2_data', api.export_ora2_data, name='export_ora2_data'),
|
||||
|
||||
|
||||
@@ -318,7 +318,6 @@ def _section_e_commerce(course, access, paid_mode, coupons_enabled, reports_enab
|
||||
'set_course_mode_url': reverse('set_course_mode_price', kwargs={'course_id': six.text_type(course_key)}),
|
||||
'download_coupon_codes_url': reverse('get_coupon_codes', kwargs={'course_id': six.text_type(course_key)}),
|
||||
'enrollment_report_url': reverse('get_enrollment_report', kwargs={'course_id': six.text_type(course_key)}),
|
||||
'exec_summary_report_url': reverse('get_exec_summary_report', kwargs={'course_id': six.text_type(course_key)}),
|
||||
'list_financial_report_downloads_url': reverse(
|
||||
'list_financial_report_downloads',
|
||||
kwargs={'course_id': six.text_type(course_key)}
|
||||
|
||||
@@ -35,7 +35,6 @@ from lms.djangoapps.instructor_task.tasks import (
|
||||
course_survey_report_csv,
|
||||
delete_problem_state,
|
||||
enrollment_report_features_csv,
|
||||
exec_summary_report_csv,
|
||||
export_ora2_data,
|
||||
generate_certificates,
|
||||
override_problem_score,
|
||||
@@ -406,20 +405,6 @@ def submit_calculate_may_enroll_csv(request, course_key, features):
|
||||
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
||||
|
||||
|
||||
def submit_executive_summary_report(request, course_key):
|
||||
"""
|
||||
Submits a task to generate a HTML File containing the executive summary report.
|
||||
|
||||
Raises AlreadyRunningError if HTML File is already being updated.
|
||||
"""
|
||||
task_type = 'exec_summary_report'
|
||||
task_class = exec_summary_report_csv
|
||||
task_input = {}
|
||||
task_key = ""
|
||||
|
||||
return submit_task(request, task_type, task_class, course_key, task_input, task_key)
|
||||
|
||||
|
||||
def submit_course_survey_report(request, course_key):
|
||||
"""
|
||||
Submits a task to generate a HTML File containing the executive summary report.
|
||||
|
||||
@@ -110,7 +110,6 @@ def generate_already_running_error_message(task_type):
|
||||
'profile_info_csv': _('enrolled learner profile'),
|
||||
'may_enroll_info_csv': _('enrollment'),
|
||||
'detailed_enrollment_report': _('detailed enrollment'),
|
||||
'exec_summary_report': _('executive summary'),
|
||||
'course_survey_report': _('survey'),
|
||||
'proctored_exam_results_report': _('proctored exam results'),
|
||||
'export_ora2_data': _('ORA data'),
|
||||
|
||||
@@ -33,7 +33,6 @@ from lms.djangoapps.instructor_task.tasks_base import BaseInstructorTask
|
||||
from lms.djangoapps.instructor_task.tasks_helper.certs import generate_students_certificates
|
||||
from lms.djangoapps.instructor_task.tasks_helper.enrollments import (
|
||||
upload_enrollment_report,
|
||||
upload_exec_summary_report,
|
||||
upload_may_enroll_csv,
|
||||
upload_students_csv
|
||||
)
|
||||
@@ -230,18 +229,6 @@ def enrollment_report_features_csv(entry_id, xmodule_instance_args):
|
||||
return run_main_task(entry_id, task_fn, action_name)
|
||||
|
||||
|
||||
@task(base=BaseInstructorTask)
|
||||
def exec_summary_report_csv(entry_id, xmodule_instance_args):
|
||||
"""
|
||||
Compute executive summary report for a course and upload the
|
||||
Html generated report to an S3 bucket for download.
|
||||
"""
|
||||
# Translators: This is a past-tense verb that is inserted into task progress messages as {action}.
|
||||
action_name = 'generating_exec_summary_report'
|
||||
task_fn = partial(upload_exec_summary_report, xmodule_instance_args)
|
||||
return run_main_task(entry_id, task_fn, action_name)
|
||||
|
||||
|
||||
@task(base=BaseInstructorTask)
|
||||
def course_survey_report_csv(entry_id, xmodule_instance_args):
|
||||
"""
|
||||
|
||||
@@ -18,15 +18,6 @@ from lms.djangoapps.instructor.paidcourse_enrollment_report import PaidCourseEnr
|
||||
from lms.djangoapps.instructor_analytics.basic import enrolled_students_features, list_may_enroll
|
||||
from lms.djangoapps.instructor_analytics.csvs import format_dictlist
|
||||
from lms.djangoapps.instructor_task.models import ReportStore
|
||||
from shoppingcart.models import (
|
||||
CouponRedemption,
|
||||
CourseRegCodeItem,
|
||||
CourseRegistrationCode,
|
||||
Invoice,
|
||||
InvoiceTransaction,
|
||||
PaidCourseRegistration,
|
||||
RegistrationCodeRedemption
|
||||
)
|
||||
from student.models import CourseAccessRole, CourseEnrollment
|
||||
from util.file import course_filename_prefix_generator
|
||||
|
||||
@@ -221,175 +212,3 @@ def upload_students_csv(_xmodule_instance_args, _entry_id, course_id, task_input
|
||||
upload_csv_to_report_store(rows, 'student_profile_info', course_id, start_date)
|
||||
|
||||
return task_progress.update_task_state(extra_meta=current_step)
|
||||
|
||||
|
||||
def get_executive_report(course_id):
|
||||
"""
|
||||
Returns dict containing information about the course executive summary.
|
||||
"""
|
||||
single_purchase_total = PaidCourseRegistration.get_total_amount_of_purchased_item(course_id)
|
||||
bulk_purchase_total = CourseRegCodeItem.get_total_amount_of_purchased_item(course_id)
|
||||
paid_invoices_total = InvoiceTransaction.get_total_amount_of_paid_course_invoices(course_id)
|
||||
gross_paid_revenue = single_purchase_total + bulk_purchase_total + paid_invoices_total
|
||||
|
||||
all_invoices_total = Invoice.get_invoice_total_amount_for_course(course_id)
|
||||
gross_pending_revenue = all_invoices_total - float(paid_invoices_total)
|
||||
|
||||
gross_revenue = float(gross_paid_revenue) + float(gross_pending_revenue)
|
||||
|
||||
refunded_self_purchased_seats = PaidCourseRegistration.get_self_purchased_seat_count(
|
||||
course_id, status='refunded'
|
||||
)
|
||||
refunded_bulk_purchased_seats = CourseRegCodeItem.get_bulk_purchased_seat_count(
|
||||
course_id, status='refunded'
|
||||
)
|
||||
total_seats_refunded = refunded_self_purchased_seats + refunded_bulk_purchased_seats
|
||||
|
||||
self_purchased_refunds = PaidCourseRegistration.get_total_amount_of_purchased_item(
|
||||
course_id,
|
||||
status='refunded'
|
||||
)
|
||||
bulk_purchase_refunds = CourseRegCodeItem.get_total_amount_of_purchased_item(course_id, status='refunded')
|
||||
total_amount_refunded = self_purchased_refunds + bulk_purchase_refunds
|
||||
|
||||
top_discounted_codes = CouponRedemption.get_top_discount_codes_used(course_id)
|
||||
total_coupon_codes_purchases = CouponRedemption.get_total_coupon_code_purchases(course_id)
|
||||
|
||||
bulk_purchased_codes = CourseRegistrationCode.order_generated_registration_codes(course_id)
|
||||
|
||||
unused_registration_codes = 0
|
||||
for registration_code in bulk_purchased_codes:
|
||||
if not RegistrationCodeRedemption.is_registration_code_redeemed(registration_code.code):
|
||||
unused_registration_codes += 1
|
||||
|
||||
self_purchased_seat_count = PaidCourseRegistration.get_self_purchased_seat_count(course_id)
|
||||
bulk_purchased_seat_count = CourseRegCodeItem.get_bulk_purchased_seat_count(course_id)
|
||||
total_invoiced_seats = CourseRegistrationCode.invoice_generated_registration_codes(course_id).count()
|
||||
|
||||
total_seats = self_purchased_seat_count + bulk_purchased_seat_count + total_invoiced_seats
|
||||
|
||||
self_purchases_percentage = 0.0
|
||||
bulk_purchases_percentage = 0.0
|
||||
invoice_purchases_percentage = 0.0
|
||||
avg_price_paid = 0.0
|
||||
|
||||
if total_seats != 0:
|
||||
self_purchases_percentage = (float(self_purchased_seat_count) / float(total_seats)) * 100
|
||||
bulk_purchases_percentage = (float(bulk_purchased_seat_count) / float(total_seats)) * 100
|
||||
invoice_purchases_percentage = (float(total_invoiced_seats) / float(total_seats)) * 100
|
||||
avg_price_paid = gross_revenue / total_seats
|
||||
|
||||
course = get_course_by_id(course_id, depth=0)
|
||||
currency = settings.PAID_COURSE_REGISTRATION_CURRENCY[1]
|
||||
|
||||
return {
|
||||
'display_name': course.display_name,
|
||||
'start_date': course.start.strftime("%Y-%m-%d") if course.start is not None else 'N/A',
|
||||
'end_date': course.end.strftime("%Y-%m-%d") if course.end is not None else 'N/A',
|
||||
'total_seats': total_seats,
|
||||
'currency': currency,
|
||||
'gross_revenue': float(gross_revenue),
|
||||
'gross_paid_revenue': float(gross_paid_revenue),
|
||||
'gross_pending_revenue': gross_pending_revenue,
|
||||
'total_seats_refunded': total_seats_refunded,
|
||||
'total_amount_refunded': float(total_amount_refunded),
|
||||
'average_paid_price': float(avg_price_paid),
|
||||
'discount_codes_data': top_discounted_codes,
|
||||
'total_seats_using_discount_codes': total_coupon_codes_purchases,
|
||||
'total_self_purchase_seats': self_purchased_seat_count,
|
||||
'total_bulk_purchase_seats': bulk_purchased_seat_count,
|
||||
'total_invoiced_seats': total_invoiced_seats,
|
||||
'unused_bulk_purchase_code_count': unused_registration_codes,
|
||||
'self_purchases_percentage': self_purchases_percentage,
|
||||
'bulk_purchases_percentage': bulk_purchases_percentage,
|
||||
'invoice_purchases_percentage': invoice_purchases_percentage,
|
||||
}
|
||||
|
||||
|
||||
def upload_exec_summary_report(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name):
|
||||
"""
|
||||
For a given `course_id`, generate a html report containing information,
|
||||
which provides a snapshot of how the course is doing.
|
||||
"""
|
||||
start_time = time()
|
||||
report_generation_date = datetime.now(UTC)
|
||||
status_interval = 100
|
||||
|
||||
enrolled_users = CourseEnrollment.objects.users_enrolled_in(course_id)
|
||||
true_enrollment_count = 0
|
||||
for user in enrolled_users:
|
||||
if not user.is_staff and not CourseAccessRole.objects.filter(
|
||||
user=user, course_id=course_id, role__in=FILTERED_OUT_ROLES
|
||||
).exists():
|
||||
true_enrollment_count += 1
|
||||
|
||||
task_progress = TaskProgress(action_name, true_enrollment_count, start_time)
|
||||
|
||||
fmt = u'Task: {task_id}, InstructorTask ID: {entry_id}, Course: {course_id}, Input: {task_input}'
|
||||
task_info_string = fmt.format(
|
||||
task_id=_xmodule_instance_args.get('task_id') if _xmodule_instance_args is not None else None,
|
||||
entry_id=_entry_id,
|
||||
course_id=course_id,
|
||||
task_input=_task_input
|
||||
)
|
||||
|
||||
TASK_LOG.info(u'%s, Task type: %s, Starting task execution', task_info_string, action_name)
|
||||
current_step = {'step': 'Gathering executive summary report information'}
|
||||
|
||||
TASK_LOG.info(
|
||||
u'%s, Task type: %s, Current step: %s, generating executive summary report',
|
||||
task_info_string,
|
||||
action_name,
|
||||
current_step
|
||||
)
|
||||
|
||||
if task_progress.attempted % status_interval == 0:
|
||||
task_progress.update_task_state(extra_meta=current_step)
|
||||
task_progress.attempted += 1
|
||||
|
||||
# get the course executive summary report information.
|
||||
data_dict = get_executive_report(course_id)
|
||||
data_dict.update(
|
||||
{
|
||||
'total_enrollments': true_enrollment_count,
|
||||
'report_generation_date': report_generation_date.strftime("%Y-%m-%d"),
|
||||
}
|
||||
)
|
||||
|
||||
# By this point, we've got the data that we need to generate html report.
|
||||
current_step = {'step': 'Uploading executive summary report HTML file'}
|
||||
task_progress.update_task_state(extra_meta=current_step)
|
||||
TASK_LOG.info(u'%s, Task type: %s, Current step: %s', task_info_string, action_name, current_step)
|
||||
|
||||
# Perform the actual upload
|
||||
_upload_exec_summary_to_store(data_dict, 'executive_report', course_id, report_generation_date)
|
||||
task_progress.succeeded += 1
|
||||
# One last update before we close out...
|
||||
TASK_LOG.info(u'%s, Task type: %s, Finalizing executive summary report task', task_info_string, action_name)
|
||||
return task_progress.update_task_state(extra_meta=current_step)
|
||||
|
||||
|
||||
def _upload_exec_summary_to_store(data_dict, report_name, course_id, generated_at, config_name='FINANCIAL_REPORTS'):
|
||||
"""
|
||||
Upload Executive Summary Html file using ReportStore.
|
||||
|
||||
Arguments:
|
||||
data_dict: containing executive report data.
|
||||
report_name: Name of the resulting Html File.
|
||||
course_id: ID of the course
|
||||
"""
|
||||
report_store = ReportStore.from_config(config_name)
|
||||
|
||||
# Use the data dict and html template to generate the output buffer
|
||||
output_buffer = StringIO(render_to_string("instructor/instructor_dashboard_2/executive_summary.html", data_dict))
|
||||
|
||||
report_store.store(
|
||||
course_id,
|
||||
u"{course_prefix}_{report_name}_{timestamp_str}.html".format(
|
||||
course_prefix=course_filename_prefix_generator(course_id),
|
||||
report_name=report_name,
|
||||
timestamp_str=generated_at.strftime("%Y-%m-%d-%H%M")
|
||||
),
|
||||
output_buffer,
|
||||
)
|
||||
tracker_emit(report_name)
|
||||
|
||||
@@ -27,7 +27,6 @@ from lms.djangoapps.instructor_task.api import (
|
||||
submit_delete_entrance_exam_state_for_student,
|
||||
submit_delete_problem_state_for_all_students,
|
||||
submit_detailed_enrollment_features_csv,
|
||||
submit_executive_summary_report,
|
||||
submit_export_ora2_data,
|
||||
submit_override_score,
|
||||
submit_rescore_entrance_exam_for_student,
|
||||
@@ -257,12 +256,6 @@ class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCa
|
||||
self.course.id)
|
||||
self._test_resubmission(api_call)
|
||||
|
||||
def test_submit_executive_summary_report(self):
|
||||
api_call = lambda: submit_executive_summary_report(
|
||||
self.create_task_request(self.instructor), self.course.id
|
||||
)
|
||||
self._test_resubmission(api_call)
|
||||
|
||||
def test_submit_course_survey_report(self):
|
||||
api_call = lambda: submit_course_survey_report(
|
||||
self.create_task_request(self.instructor), self.course.id
|
||||
|
||||
@@ -44,7 +44,6 @@ from lms.djangoapps.instructor_analytics.basic import UNAVAILABLE, list_problem_
|
||||
from lms.djangoapps.instructor_task.tasks_helper.certs import generate_students_certificates
|
||||
from lms.djangoapps.instructor_task.tasks_helper.enrollments import (
|
||||
upload_enrollment_report,
|
||||
upload_exec_summary_report,
|
||||
upload_may_enroll_csv,
|
||||
upload_students_csv
|
||||
)
|
||||
@@ -1030,49 +1029,6 @@ class TestProblemReportCohortedContent(TestReportMixin, ContentGroupTestCase, In
|
||||
self.verify_rows_in_csv(expected_grades)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestExecutiveSummaryReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
"""
|
||||
Tests that Executive Summary report generation works.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestExecutiveSummaryReport, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
min_price=50,
|
||||
mode_slug=CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
|
||||
)
|
||||
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.student1 = UserFactory()
|
||||
self.student2 = UserFactory()
|
||||
|
||||
def test_successfully_generate_executive_summary_report(self):
|
||||
"""
|
||||
Test that successfully generates the executive summary report.
|
||||
"""
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_exec_summary_report(
|
||||
None, None, self.course.id,
|
||||
task_input, 'generating executive summary report'
|
||||
)
|
||||
ReportStore.from_config(config_name='FINANCIAL_REPORTS')
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
|
||||
def _verify_html_file_report(self, report_store, expected_data):
|
||||
"""
|
||||
Verify grade report data.
|
||||
"""
|
||||
report_html_filename = report_store.links_for(self.course.id)[0][0]
|
||||
report_path = report_store.path_to(self.course.id, report_html_filename)
|
||||
with report_store.storage.open(report_path) as html_file:
|
||||
html_file_data = html_file.read().decode('utf-8')
|
||||
for data in expected_data:
|
||||
self.assertIn(data, html_file_data)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestCourseSurveyReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
"""
|
||||
|
||||
@@ -1,691 +0,0 @@
|
||||
<%page args="section_data" expression_filter="h"/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from datetime import datetime, timedelta
|
||||
from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
import pytz
|
||||
%>
|
||||
<%include file="add_coupon_modal.html" args="section_data=section_data" />
|
||||
<%include file="edit_coupon_modal.html" args="section_data=section_data" />
|
||||
<%include file="set_course_mode_price_modal.html" args="section_data=section_data" />
|
||||
<%include file="generate_registarion_codes_modal.html" args="section_data=section_data" />
|
||||
<%include file="invalidate_registration_code_modal.html" args="section_data=section_data" />
|
||||
<div class="ecommerce-wrapper">
|
||||
<div class="error-msgs" id="error-msg"></div>
|
||||
<div id = "accordion">
|
||||
%if not section_data['is_ecommerce_course']:
|
||||
<div class="wrap">
|
||||
<h3 class="hd hd-3">${_('Enrollment Codes')}</h3>
|
||||
<div>
|
||||
%if section_data['sales_admin']:
|
||||
<span class="code_tip">
|
||||
<p>${_('Create one or more pre-paid course enrollment codes. Students can use these codes to enroll in the course.')}</p>
|
||||
<a id="registration_code_generation_link" href="#reg_code_generation_modal" class="add blue-button">${_('Create Enrollment Codes')}</a>
|
||||
<p></p>
|
||||
<p>${_('Cancel, restore, or mark an enrollment code as unused.')}</p>
|
||||
<a id="query_registration_code_link" href="#invalidate_registration_code_modal" rel="leanModal" class="add blue-button">${_('Change Enrollment Code Status')}</a>
|
||||
</span>
|
||||
%endif
|
||||
<p>${_('Download a .csv file of all enrollment codes for this course.')}</p>
|
||||
<p>
|
||||
<form action="${ section_data['get_registration_code_csv_url'] }" id="download_registration_codes" method="post">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
|
||||
<input type="text" name="download_company_name" placeholder="Company Name (optional)"/>
|
||||
<input type="submit" name="list-registration-codes-csv" value="${_("Download All Enrollment Codes")}" data-csv="true">
|
||||
</form>
|
||||
</p>
|
||||
<p>${_('Download a .csv file of all unused enrollment codes for this course.')}</p>
|
||||
<p>
|
||||
<form action="${ section_data['active_registration_code_csv_url'] }" id="active_registration_codes" method="post">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
|
||||
<input type="text" name="active_company_name" placeholder="Company Name (optional)"/>
|
||||
<input type="submit" name="active-registration-codes-csv" value="${_("Download Unused Enrollment Codes")}" data-csv="true">
|
||||
</form>
|
||||
</p>
|
||||
<p>${_('Download a .csv file of all used enrollment codes for this course.')}</p>
|
||||
<p>
|
||||
<form action="${ section_data['spent_registration_code_csv_url'] }" id="spent_registration_codes" method="post">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
|
||||
<input type="text" name="spent_company_name" placeholder="Company Name (optional)"/>
|
||||
<input type="submit" name="spent-registration-codes-csv" value="${_("Download Used Enrollment Codes")}" data-csv="true">
|
||||
</form>
|
||||
</p>
|
||||
<a id="registration_code_generation_link-trigger" href="#registration_code_generation_modal" rel="leanModal"></a>
|
||||
</div>
|
||||
</div>
|
||||
%endif
|
||||
<!-- end wrap -->
|
||||
%if section_data['coupons_enabled']:
|
||||
<div class="wrap">
|
||||
<h3 class="hd hd-3">${_("Course Price")}</h3>
|
||||
<div>
|
||||
<span class="tip">${_("Course price per seat: ")}<span>${section_data['currency_symbol']}${section_data['course_price']}</span>
|
||||
%if section_data['access']['finance_admin'] is True:
|
||||
<a id="course_price_link" href="#set-course-mode-price-modal" rel="leanModal" class="add blue-button">+ ${_('Edit Price')}</a>
|
||||
%endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
%endif
|
||||
<!-- end wrap -->
|
||||
%if section_data['access']['finance_admin']:
|
||||
<div class="wrap">
|
||||
<h3 class="hd hd-3">${_("Course Seat Purchases")}</h3>
|
||||
<div>
|
||||
%if section_data['total_amount'] is not None:
|
||||
<span><strong>${_("Total Credit Card Purchases: ")}</strong></span><span>${section_data['currency_symbol']}${section_data['total_amount']}</span>
|
||||
%endif
|
||||
<span class="csv_tip">
|
||||
<div>
|
||||
<p>${_("Download a .csv file for all credit card purchases or for all invoices, regardless of status.")}</p>
|
||||
<input type="button" class="add blue-button" name="list-sale-csv" value="${_("Download All Invoices")}" 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 Credit Card Purchases")}" data-endpoint="${ section_data['get_sale_order_records_url'] }" data-csv="true">
|
||||
</div>
|
||||
</span>
|
||||
<hr>
|
||||
<p>${_("To cancel or resubmit an invoice, enter the invoice number below.")}</p>
|
||||
<span class="invalid_sale">
|
||||
<input type="number" id="invoice_number" placeholder= "${_("Invoice Number")}"/>
|
||||
<input type="button" class="add blue-button" id="invalidate_invoice" value="${_("Cancel Invoice")}">
|
||||
<input type="button" class="add blue-button" id="re_validate_invoice" value="${_("Resubmit Invoice")}">
|
||||
</span>
|
||||
|
||||
</div>
|
||||
</div><!-- end wrap -->
|
||||
%endif
|
||||
%if section_data['reports_enabled']:
|
||||
<div class="reports wrap">
|
||||
<h3 class="hd hd-3">${_("Reports")}</h3>
|
||||
<div>
|
||||
<span class="csv_tip">
|
||||
<div>
|
||||
<p>${_("Create a .csv file that contains enrollment information for your course.")}</p>
|
||||
<input type="button" class="add blue-button" name="user-enrollment-report" value="${_("Create Enrollment Report")}" data-endpoint="${ section_data['enrollment_report_url'] }">
|
||||
</div>
|
||||
<div class="request-response msg msg-confirm copy" id="enrollment-report-request-response"></div>
|
||||
<div class="request-response-error msg msg-warning copy" id="enrollment-report-request-response-error"></div>
|
||||
<br>
|
||||
</span>
|
||||
<span class="csv_tip">
|
||||
<div>
|
||||
<p>${_("Create an HTML file that contains an executive summary for this course.")}</p>
|
||||
<input type="button" class="add blue-button" name="exec-summary-report" value="${_("Create Executive Summary")}" data-endpoint="${ section_data['exec_summary_report_url'] }">
|
||||
</div>
|
||||
<div class="request-response msg msg-confirm copy" id="exec-summary-report-request-response"></div>
|
||||
<div class="request-response-error msg msg-warning copy" id="exec-summary-report-request-response-error"></div>
|
||||
<br>
|
||||
</span>
|
||||
<div class="reports-download-container action-type-container">
|
||||
<p><b>${_("Available Reports")}</b></p>
|
||||
<p>${_("The following reports are available for download. Reports are not deleted. A link to every report remains available on this page, identified by the date and time (in UTC) that the report was generated.")}</p>
|
||||
|
||||
## Translators: a table of URL links to report files appears after this sentence.
|
||||
<p>
|
||||
${Text(_("{strong_start}Note{strong_end}: To help protect learner data, links to these reports that you save outside of this page or that you send or receive in email expire after five minutes.")).format(
|
||||
strong_start=HTML("<strong>"),
|
||||
strong_end=HTML("</strong>"),
|
||||
)}
|
||||
</p><br>
|
||||
|
||||
<div class="report-downloads-table" id="report-downloads-table"
|
||||
data-endpoint="${ section_data['list_financial_report_downloads_url'] }"></div>
|
||||
</div>
|
||||
|
||||
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'):
|
||||
<div class="running-tasks-container action-type-container">
|
||||
<hr>
|
||||
<h3 class="hd hd-3">${_("Pending Tasks")}</h3>
|
||||
<div class="running-tasks-section">
|
||||
<p>${_("The status for any active tasks appears in a table below.")} </p>
|
||||
<br/>
|
||||
<div class="running-tasks-table" data-endpoint="${ section_data['list_instructor_tasks_url'] }"></div>
|
||||
</div>
|
||||
<div class="no-pending-tasks-message"></div>
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
</div><!-- end wrap -->
|
||||
%endif
|
||||
%if section_data['coupons_enabled'] and not section_data['is_ecommerce_course']:
|
||||
<div class="wrap">
|
||||
<h3 class="hd hd-3">${_("Coupon Code List")}</h3>
|
||||
<div>
|
||||
|
||||
<span class="csv_tip">${_("Download a .csv file of all coupon codes for this course.")}
|
||||
<input class="add blue-button" type="button" name="download-coupon-codes-csv" value="${_("Download Coupon Codes")}" data-endpoint="${ section_data['download_coupon_codes_url'] }" data-csv="true">
|
||||
</span>
|
||||
|
||||
<span class="tip">${_("Coupon Codes")} <a id="add_coupon_link" href="#add-coupon-modal" rel="leanModal"
|
||||
class="add blue-button">+ ${_("Add Coupon Code")}</a></span>
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
%if len(section_data['coupons']):
|
||||
<table class="coupons-table">
|
||||
<thead>
|
||||
<tr class="coupons-headings">
|
||||
<th class="c_code">${_("Coupon Code")}</th>
|
||||
<th class="c_dsc">${_("Description")}</th>
|
||||
<th class="c_expiry">${_("Expiration Date")}</th>
|
||||
<th class="c_discount">${_("Coupon (%)")}</th>
|
||||
<th class="c_count">${_("Number Redeemed")}</th>
|
||||
<th class="c_action">${_("Actions")}</th>
|
||||
</tr>
|
||||
</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">
|
||||
%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>${_('{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>
|
||||
%endfor
|
||||
</tbody>
|
||||
</table>
|
||||
<a id="edit-modal-trigger" href="#edit-coupon-modal" rel="leanModal"></a>
|
||||
%endif
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
%endif
|
||||
<!-- end wrap -->
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
$(function () {
|
||||
var icons = {
|
||||
header: "ui-icon-carat-1-e",
|
||||
activeHeader: "ui-icon-carat-1-s"
|
||||
};
|
||||
var act = 0;
|
||||
$("#accordion").accordion(
|
||||
{
|
||||
heightStyle: 'content',
|
||||
activate: function(event, ui) {
|
||||
var active = jQuery("#accordion").accordion('option', 'active');
|
||||
$.cookie('saved_index', null);
|
||||
$.cookie('saved_index', active);
|
||||
$('#error-msg').val('');
|
||||
$("#error-msg").fadeOut(2000 , function(){
|
||||
$('#error-msg').hide()
|
||||
});
|
||||
},
|
||||
animate: 400,
|
||||
header: "> div.wrap >h2",
|
||||
icons:icons,
|
||||
active:isNaN(parseInt($.cookie('saved_index'))) ? 0 : parseInt($.cookie('saved_index')),
|
||||
collapsible: true
|
||||
});
|
||||
|
||||
$('a[rel*=leanModal]').leanModal({ top : -70, position: "absolute", closeButton: ".modal_close" });
|
||||
$.each($("a.edit-right"), function () {
|
||||
if ($(this).parent().parent('tr').hasClass('inactive_coupon')) {
|
||||
$(this).removeAttr('href')
|
||||
}
|
||||
});
|
||||
$.each($("a.remove_coupon"), function () {
|
||||
if ($(this).parent().parent('tr').hasClass('inactive_coupon')) {
|
||||
$(this).removeAttr('href')
|
||||
}
|
||||
});
|
||||
$('#registration_code_generation_link').click(function(event){
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "${section_data['get_user_invoice_preference_url'] | n, js_escaped_string}",
|
||||
success: function (data) {
|
||||
$('#invoice-copy').prop('checked', data.invoice_copy);
|
||||
$('#registration_code_generation_link-trigger').click();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
$('#invalidate_invoice, #re_validate_invoice').click(function (event) {
|
||||
event.preventDefault();
|
||||
var event_type = "re_validate"
|
||||
if($(event.target).attr('id')=='invalidate_invoice'){
|
||||
event_type = "invalidate"
|
||||
}
|
||||
if($('#invoice_number').val() == "") {
|
||||
$('#error-msg').attr('class','error-msgs')
|
||||
$('#error-msg').text("${_('The Invoice Number field cannot be empty.') | n, js_escaped_string}").show();
|
||||
return
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
data: {invoice_number: $('#invoice_number').val(), event_type:event_type},
|
||||
url: "${section_data['sale_validation_url'] | n, js_escaped_string}",
|
||||
success: function (data) {
|
||||
$('#error-msg').attr('class','success-msgs')
|
||||
edx.HtmlUtils.setHtml($('#error-msg'), data.message);
|
||||
$('#error-msg').show();
|
||||
$('#invoice_number').val('');
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
$('#error-msg').attr('class','error-msgs')
|
||||
edx.HtmlUtils.setHtml($('#error-msg'), jqXHR.responseText);
|
||||
$('#error-msg').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('a.edit-right').click(function (event) {
|
||||
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: none');
|
||||
$('#edit_coupon_form #coupon_form_error').text();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var coupon_id = $(this).data('item-id');
|
||||
$('#coupon_id').val(coupon_id);
|
||||
if ($(this).parent().parent('tr').hasClass('inactive_coupon')) {
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
data: {id: coupon_id},
|
||||
url: "${section_data['ajax_get_coupon_info'] | n, js_escaped_string}",
|
||||
success: function (data) {
|
||||
$('#error-msg').val('');
|
||||
$('#error-msg').hide()
|
||||
$('input#edit_coupon_code').val(data.coupon_code);
|
||||
$('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("${_('No Expiration Date') | n, js_escaped_string}");
|
||||
}
|
||||
$('#edit-modal-trigger').click();
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var data = $.parseJSON(jqXHR.responseText);
|
||||
$('#error-msg').attr('class','error-msgs')
|
||||
edx.HtmlUtils.setHtml($('#error-msg'), data.message);
|
||||
$('#error-msg').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
$('a.remove_coupon').click(function (event) {
|
||||
var anchor = $(this);
|
||||
if (anchor.data("disabled")) {
|
||||
return false;
|
||||
}
|
||||
anchor.data("disabled", "disabled");
|
||||
event.preventDefault();
|
||||
if ($(this).parent().parent('tr').hasClass('inactive_coupon')) {
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
data: {id: $(this).data('item-id')},
|
||||
url: "${section_data['ajax_remove_coupon_url'] | n, js_escaped_string}",
|
||||
success: function (data) {
|
||||
anchor.removeData("disabled");
|
||||
location.reload(true);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var data = $.parseJSON(jqXHR.responseText);
|
||||
$('#error-msg').attr('class','error-msgs')
|
||||
edx.HtmlUtils.setHtml($('#error-msg'), data.message);
|
||||
$('#error-msg').show();
|
||||
anchor.removeData("disabled");
|
||||
}
|
||||
});
|
||||
});
|
||||
var generate_registration_code_form = $("form#generate_codes");
|
||||
var generate_registration_button = $('input[name="generate-registration-codes-csv"]');
|
||||
var registration_code_error = $('#generate_codes #registration_code_form_error');
|
||||
|
||||
function validateEmail(sEmail) {
|
||||
filter = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/
|
||||
return filter.test(sEmail)
|
||||
}
|
||||
generate_registration_code_form.submit(function () {
|
||||
registration_code_error.attr('style', 'display: none');
|
||||
generate_registration_button.attr('disabled', true);
|
||||
var total_registration_codes = $('input[name="total_registration_codes"]').val();
|
||||
var recipient_name = $('input[name="recipient_name"]').val();
|
||||
var recipient_email = $('input[name="recipient_email"]').val();
|
||||
var unit_price = $('input[name="unit_price"]').val();
|
||||
var company_name = $('input[name="company_name"]').val();
|
||||
var company_contact_name = $('input[name="company_contact_name"]').val();
|
||||
var company_contact_email = $('input[name="company_contact_email"]').val();
|
||||
|
||||
var address_line = $('input[name="address_line_1"]').val();
|
||||
|
||||
if (company_name == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the company name.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (($.isNumeric(company_name))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('The company name cannot be a number.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (company_contact_name == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the company contact name.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (($.isNumeric(company_contact_name))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('The company contact name cannot be a number.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (company_contact_email == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the email address for the company contact.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (!(validateEmail(company_contact_email))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter a valid email address.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (recipient_name == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the recipient name.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (($.isNumeric(recipient_name))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('The recipient name cannot be a number.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (recipient_email == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the recipient email address.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (!(validateEmail(recipient_email))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter a valid email address.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (address_line == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the billing address.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (unit_price == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the price per course seat.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false
|
||||
}
|
||||
if (!($.isNumeric(unit_price))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter a numeric value for the price per course seat. Do not include currency symbols.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false
|
||||
}
|
||||
if (total_registration_codes == '') {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter the number of enrollment codes.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false
|
||||
}
|
||||
if (!($.isNumeric(total_registration_codes))) {
|
||||
registration_code_error.attr('style', 'display: block !important');
|
||||
registration_code_error.text("${_('Enter a numeric value for the number of enrollment codes.') | n, js_escaped_string}");
|
||||
generate_registration_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
var modal_overLay = $('#lean_overlay');
|
||||
var registration_code_modal = $('#registration_code_generation_modal');
|
||||
registration_code_modal.hide();
|
||||
modal_overLay.hide();
|
||||
});
|
||||
|
||||
$('#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'] | n, js_escaped_string}",
|
||||
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();
|
||||
});
|
||||
$('#query_registration_code_link').click(function () {
|
||||
reset_input_fields();
|
||||
});
|
||||
$('#add_coupon_link').click(function () {
|
||||
reset_input_fields();
|
||||
});
|
||||
$('#registration_code_generation_link').click(function () {
|
||||
reset_input_fields();
|
||||
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
|
||||
});
|
||||
$('#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());
|
||||
var currency = $.trim($('#course_mode_currency').val());
|
||||
|
||||
// Check if empty of not
|
||||
if (course_price === '') {
|
||||
$('#set_price_form #course_form_error').attr('style', 'display: block !important');
|
||||
$('#set_price_form #course_form_error').text("${_('Enter the price per course seat.') | n, js_escaped_string}");
|
||||
$("#set_course_button").removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (!$.isNumeric(course_price)) {
|
||||
$("#set_course_button").removeAttr('disabled');
|
||||
$('#set_price_form #course_form_error').attr('style', 'display: block !important');
|
||||
$('#set_price_form #course_form_error').text("${_('Enter a numeric value for the price per course seat. Do not include currency symbols.') | n, js_escaped_string}");
|
||||
return false;
|
||||
}
|
||||
if (currency == '') {
|
||||
$('#set_price_form #course_form_error').attr('style', 'display: block !important');
|
||||
$('#set_price_form #course_form_error').text("${_('Select a currency.') | n, js_escaped_string}");
|
||||
$("#set_course_button").removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
data: {
|
||||
"course_price" : course_price,
|
||||
"currency": currency
|
||||
},
|
||||
url: "${section_data['set_course_mode_url'] | n, js_escaped_string}",
|
||||
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_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());
|
||||
var expiration_date = $.trim($('#coupon_expiration_date').val());
|
||||
|
||||
// Check if empty of not
|
||||
if (code === '') {
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
|
||||
$('#add_coupon_form #coupon_form_error').text("${_('Enter a coupon code.') | n, js_escaped_string}");
|
||||
return false;
|
||||
}
|
||||
if (parseInt(coupon_discount) > 100) {
|
||||
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
|
||||
$('#add_coupon_form #coupon_form_error').text("${_('The discount percentage must be less than or equal to 100.') | n, js_escaped_string}");
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
if (!$.isNumeric(coupon_discount)) {
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
|
||||
$('#add_coupon_form #coupon_form_error').text("${_('Enter a numeric value for the discount amount. Do not include the percent sign.') | n, js_escaped_string}");
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
data: {
|
||||
"code" : code,
|
||||
"discount": coupon_discount,
|
||||
"course_id": course_id,
|
||||
"description": description,
|
||||
"expiration_date": expiration_date
|
||||
},
|
||||
url: "${section_data['ajax_add_coupon'] | n, js_escaped_string}",
|
||||
success: function (data) {
|
||||
location.reload(true);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var data = $.parseJSON(jqXHR.responseText);
|
||||
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
|
||||
$('#add_coupon_form #coupon_form_error').text(data.message);
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
});
|
||||
// removing close link's default behavior
|
||||
$('.close-modal').click(function (e) {
|
||||
$("#update_coupon_button").removeAttr('disabled');
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
$("#set_course_button").removeAttr('disabled');
|
||||
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
|
||||
reset_input_fields();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
var onModalClose = function () {
|
||||
$("#add-coupon-modal").attr("aria-hidden", "true");
|
||||
$(".remove_coupon").focus();
|
||||
$("#edit-coupon-modal").attr("aria-hidden", "true");
|
||||
$(".edit-right").focus();
|
||||
$("#set-course-mode-price-modal").attr("aria-hidden", "true");
|
||||
$("#invalidate_registration_code_modal").attr("aria-hidden", "true");
|
||||
|
||||
$("#registration_code_generation_modal").attr("aria-hidden", "true");
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
$("#set_course_button").removeAttr('disabled');
|
||||
$("#update_coupon_button").removeAttr('disabled');
|
||||
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
|
||||
reset_input_fields();
|
||||
};
|
||||
|
||||
var cycle_modal_tab = function (from_element_name, to_element_name) {
|
||||
$(from_element_name).on('keydown', function (e) {
|
||||
var keyCode = e.keyCode || e.which;
|
||||
var TAB_KEY = 9; // 9 corresponds to the tab key
|
||||
if (keyCode === TAB_KEY) {
|
||||
e.preventDefault();
|
||||
$(to_element_name).focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$("#add-coupon-modal .close-modal").click(onModalClose);
|
||||
$("#edit-coupon-modal .close-modal").click(onModalClose);
|
||||
$('#registration_code_generation_modal .close-modal').click(onModalClose);
|
||||
$("#set-course-mode-price-modal .close-modal").click(reset_input_fields);
|
||||
$("#invalidate_registration_code_modal .close-modal").click(reset_input_fields);
|
||||
|
||||
|
||||
// Hitting the ESC key will exit the modal
|
||||
$("#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal, #invalidate_registration_code_modal, #registration_code_generation_modal").on("keydown", function (e) {
|
||||
var keyCode = e.keyCode || e.which;
|
||||
// 27 is the ESC key
|
||||
if (keyCode === 27) {
|
||||
e.preventDefault();
|
||||
$("#add-coupon-modal .close-modal").click();
|
||||
$("#set-course-mode-price-modal .close-modal").click();
|
||||
$("#edit-coupon-modal .close-modal").click();
|
||||
$("#invalidate_registration_code_modal .close-modal").click();
|
||||
|
||||
$('#registration_code_generation_modal .close-modal').click();
|
||||
}
|
||||
});
|
||||
});
|
||||
var reset_input_fields = function () {
|
||||
$('#error-msg').val('');
|
||||
$('#error-msg').hide();
|
||||
$('#add_coupon_form #coupon_form_error').attr('style', 'display: none');
|
||||
$("form#set_regcode_status_form").next().remove();
|
||||
$('#set_regcode_status_form #regcode_status_form_error').attr('style', 'display: none');
|
||||
$('#set_regcode_status_form #regcode_status_form_success').attr('style', 'display: none');
|
||||
$('#set_regcode_status_form input#lookup_regcode').removeAttr('disabled');
|
||||
$('#set_price_form #course_form_error').attr('style', 'display: none');
|
||||
$('#generate_codes #registration_code_form_error').attr('style', 'display: none');
|
||||
$('#add_coupon_form #coupon_form_error').text();
|
||||
$('input#mode_price').val('');
|
||||
$('input#coupon_code').val('');
|
||||
$('input#coupon_discount').val('');
|
||||
$('textarea#coupon_description').val('');
|
||||
$('input[name="company_name"]').val('');
|
||||
$('input[name="regcode_code"]').val('');
|
||||
$('input[name="total_registration_codes"]').val('');
|
||||
$('input[name="address_line_1"]').val('');
|
||||
$('input[name="address_line_2"]').val('');
|
||||
$('input[name="address_line_3"]').val('');
|
||||
$('input[name="city"]').val('');
|
||||
$('input[name="state"]').val('');
|
||||
$('input[name="zip"]').val('');
|
||||
$('input[name="country"]').val('');
|
||||
$('input[name="customer_reference_number"]').val('');
|
||||
$('input[name="recipient_name"]').val('');
|
||||
$('input[name="unit_price"]').val('');
|
||||
$('input[name="recipient_email"]').val('');
|
||||
$('input[name="company_contact_name"]').val('');
|
||||
$('input[name="company_contact_email"]').val('');
|
||||
$('input[name="invoice"]').attr('checked', 'checked');
|
||||
$('input[name="company_reference"]').val('');
|
||||
$('input[name="internal_reference"]').val('');
|
||||
}
|
||||
</script>
|
||||
@@ -1,140 +0,0 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Executive Summary</title>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size:14px;
|
||||
line-height:22px;
|
||||
margin: 10px;
|
||||
}
|
||||
.box-bg {
|
||||
background:#f1f1f1;
|
||||
padding:10px;
|
||||
}
|
||||
th {
|
||||
padding:5px;
|
||||
background:#ccc;
|
||||
}
|
||||
h2 {
|
||||
margin-top:0
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<table width="650" border="0" cellspacing="5" cellpadding="5">
|
||||
<tr>
|
||||
<td align="left" valign="top" class="box-bg"><h2>${_("Executive Summary for {display_name}").format(display_name=display_name)}</h2>
|
||||
<table width="100%">
|
||||
|
||||
<tr>
|
||||
<td width="300">${_("Course Start Date")}</td>
|
||||
<td align="right"> ${start_date}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="300">${_("Course End Date")}</td>
|
||||
<td align="right"> ${end_date}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="300">${_("Report Creation Date")}</td>
|
||||
<td align="right"> ${report_generation_date}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="300">${_("Number of Seats")}</td>
|
||||
<td align="right">${total_seats}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="300">${_("Number of Enrollments")}</td>
|
||||
<td align="right">${total_enrollments}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Gross Revenue")}</td>
|
||||
<td align="right">${currency}${"{0:0.2f}".format(gross_revenue)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Gross Revenue Collected")}</td>
|
||||
<td align="right">${currency}${"{0:0.2f}".format(gross_paid_revenue)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Gross Revenue Pending")}</td>
|
||||
<td align="right">${currency}${"{0:0.2f}".format(gross_pending_revenue)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Number of Enrollment Refunds")}</td>
|
||||
<td align="right">${total_seats_refunded}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Amount Refunded")}</td>
|
||||
<td align="right">${currency}${"{0:0.2f}".format(total_amount_refunded)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Average Price per Seat")}</td>
|
||||
<td align="right">${currency}${"{0:0.2f}".format(average_paid_price)}</td>
|
||||
</tr>
|
||||
</table></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="top" class="box-bg"><h3>${_("Frequently Used Coupon Codes")}</h3>
|
||||
<table width="500">
|
||||
<tr>
|
||||
<td>${_("Number of seats purchased using coupon codes")}</td>
|
||||
<td>${total_seats_using_discount_codes['coupon__count']}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table width="100%">
|
||||
|
||||
<th>${_("Rank")}</th>
|
||||
<th>${_("Coupon Code")}</th>
|
||||
<th>${_("Percent Discount")}</th>
|
||||
<th>${_("Times Used")}</th>
|
||||
%for i, discount_code_data in enumerate(discount_codes_data):
|
||||
<tr>
|
||||
<td align="center">${i+1}</td>
|
||||
<td align="center">${discount_code_data['coupon__code']}</td>
|
||||
<td align="center">${discount_code_data['coupon__percentage_discount']}</td>
|
||||
<td align="center">${discount_code_data['coupon__used_count']}</td>
|
||||
</tr>
|
||||
%endfor
|
||||
</table></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" valign="top" class="box-bg"><h3>${_("Bulk and Single Seat Purchases")}</h3>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td>${_("Number of seats purchased individually")}</td>
|
||||
<td align="right">${total_self_purchase_seats}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Number of seats purchased in bulk")}</td>
|
||||
<td align="right">${total_bulk_purchase_seats}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Number of seats purchased with invoices")}</td>
|
||||
<td align="right">${total_invoiced_seats}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Unused bulk purchase seats (revenue at risk)")}</td>
|
||||
<td align="right">${unused_bulk_purchase_code_count}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Percentage of seats purchased individually")}</td>
|
||||
<td align="right">${"{0:0.2f}".format(self_purchases_percentage)}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Percentage of seats purchased in bulk")}</td>
|
||||
<td align="right">${"{0:0.2f}".format(bulk_purchases_percentage)}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>${_("Percentage of seats purchased with invoices")}</td>
|
||||
<td align="right">${"{0:0.2f}".format(invoice_purchases_percentage)}%</td>
|
||||
</tr>
|
||||
</table></td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user