diff --git a/openedx/features/enterprise_support/signals.py b/openedx/features/enterprise_support/signals.py index ec11d2e8ff..7e2041d17a 100644 --- a/openedx/features/enterprise_support/signals.py +++ b/openedx/features/enterprise_support/signals.py @@ -6,6 +6,7 @@ This module contains signals related to enterprise. import logging import six +from django.conf import settings from django.contrib.auth.models import User from django.db.models.signals import post_save, pre_save from django.dispatch import receiver @@ -13,10 +14,12 @@ from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomer, En from integrated_channels.integrated_channel.tasks import transmit_single_learner_data from email_marketing.tasks import update_user +from openedx.core.djangoapps.commerce.utils import ecommerce_api_client from openedx.core.djangoapps.signals.signals import COURSE_GRADE_NOW_PASSED from openedx.features.enterprise_support.api import enterprise_enabled from openedx.features.enterprise_support.tasks import clear_enterprise_customer_data_consent_share_cache from openedx.features.enterprise_support.utils import clear_data_consent_share_cache, is_enterprise_learner +from student.signals import UNENROLL_DONE log = logging.getLogger(__name__) @@ -80,3 +83,30 @@ def handle_enterprise_learner_passing_grade(sender, user, course_id, **kwargs): } transmit_single_learner_data.apply_async(kwargs=kwargs) + + +@receiver(UNENROLL_DONE) +def refund_order_voucher(sender, course_enrollment=None, skip_refund=False, **kwargs): # pylint: disable=unused-argument + """ + Call the /api/v2/enterprise/coupons/create_refunded_voucher/ API to create new voucher and assign it to user. + """ + + if skip_refund: + return + if not course_enrollment.refundable(): + return + if not EnterpriseCourseEnrollment.objects.filter( + enterprise_customer_user__user_id=course_enrollment.user_id, + course_id=str(course_enrollment.course.id) + ).exists(): + return + + service_user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME) + client = ecommerce_api_client(service_user) + order_number = course_enrollment.get_order_attribute_value('order_number') + if order_number: + client.enterprise.coupons.create_refunded_voucher.post( + { + "order": order_number + } + ) diff --git a/openedx/features/enterprise_support/tests/test_signals.py b/openedx/features/enterprise_support/tests/test_signals.py index 8b8e9a2f89..bd1737d278 100644 --- a/openedx/features/enterprise_support/tests/test_signals.py +++ b/openedx/features/enterprise_support/tests/test_signals.py @@ -2,25 +2,31 @@ import logging +from datetime import timedelta import ddt -from django.test import TestCase from django.test.utils import override_settings -from mock import patch - +from django.utils.timezone import now from edx_django_utils.cache import TieredCache +from mock import patch from opaque_keys.edx.keys import CourseKey +from course_modes.tests.factories import CourseModeFactory from lms.djangoapps.certificates.signals import listen_for_passing_grade -from student.tests.factories import UserFactory +from openedx.core.djangoapps.commerce.utils import ECOMMERCE_DATE_FORMAT +from openedx.core.djangoapps.credit.tests.test_api import TEST_ECOMMERCE_WORKER from openedx.core.djangoapps.signals.signals import COURSE_GRADE_NOW_PASSED +from openedx.features.enterprise_support.tests import FEATURES_WITH_ENTERPRISE_ENABLED from openedx.features.enterprise_support.tests.factories import ( EnterpriseCourseEnrollmentFactory, EnterpriseCustomerFactory, EnterpriseCustomerUserFactory ) -from openedx.features.enterprise_support.tests import FEATURES_WITH_ENTERPRISE_ENABLED from openedx.features.enterprise_support.utils import get_data_consent_share_cache_key +from student.models import CourseEnrollmentAttribute +from student.tests.factories import CourseEnrollmentFactory, UserFactory +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory log = logging.getLogger(__name__) @@ -31,12 +37,14 @@ TEST_EMAIL = "test@edx.org" @ddt.ddt @override_settings(FEATURES=FEATURES_WITH_ENTERPRISE_ENABLED) -class EnterpriseSupportSignals(TestCase): +@override_settings(ECOMMERCE_SERVICE_WORKER_USERNAME=TEST_ECOMMERCE_WORKER) +class EnterpriseSupportSignals(SharedModuleStoreTestCase): """ Tests for the enterprise support signals. """ def setUp(self): + UserFactory.create(username=TEST_ECOMMERCE_WORKER) self.user = UserFactory.create(username='test', email=TEST_EMAIL) self.course_id = 'course-v1:edX+DemoX+Demo_Course' self.enterprise_customer = EnterpriseCustomerFactory() @@ -108,6 +116,44 @@ class EnterpriseSupportSignals(TestCase): self.assertFalse(self._is_dsc_cache_found(self.user.id, self.course_id)) + @ddt.data( + (True, True, 2, False), # test if skip_refund + (False, True, 20, False), # test refundable time passed + (False, False, 2, False), # test not enterprise enrollment + (False, True, 2, True), # success: no skip_refund, is enterprise enrollment and still in refundable window. + ) + @ddt.unpack + def test_refund_order_voucher(self, skip_refund, enterprise_enrollment_exists, no_of_days_placed, api_called): + """Test refund_order_voucher signal""" + # import pdb; pdb.set_trace() + date_placed = now() - timedelta(days=no_of_days_placed) + course = CourseFactory.create(display_name='test course', run="Testing_course", start=date_placed) + enrollment = CourseEnrollmentFactory( + course_id=course.id, + user=self.user, + mode="verified", + ) + CourseModeFactory.create(course_id=course.id, mode_slug='verified') + CourseEnrollmentAttribute.objects.create( + enrollment=enrollment, + name='date_placed', + namespace='order', + value=date_placed.strftime(ECOMMERCE_DATE_FORMAT) + ) + CourseEnrollmentAttribute.objects.create( + enrollment=enrollment, + name='order_number', + namespace='order', + value='EDX-000000001' + ) + + if enterprise_enrollment_exists: + self._create_enterprise_enrollment(self.user.id, course.id) + + with patch('openedx.features.enterprise_support.signals.ecommerce_api_client') as mock_ecommerce_api_client: + enrollment.update_enrollment(is_active=False, skip_refund=skip_refund) + self.assertEqual(mock_ecommerce_api_client.called, api_called) + def test_handle_enterprise_learner_passing_grade(self): """ Test to assert transmit_single_learner_data is called when COURSE_GRADE_NOW_PASSED signal is fired