mark support (re)issued entitlements as unrefundable
This commit is contained in:
@@ -35,6 +35,7 @@ class CourseEntitlementSerializer(serializers.ModelSerializer):
|
||||
'created',
|
||||
'modified',
|
||||
'mode',
|
||||
'refund_locked',
|
||||
'order_number',
|
||||
'support_details'
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ class EntitlementsSerializerTests(ModuleStoreTestCase):
|
||||
'expired_at': entitlement.expired_at,
|
||||
'course_uuid': str(entitlement.course_uuid),
|
||||
'mode': entitlement.mode,
|
||||
'refund_locked': False,
|
||||
'enrollment_course_run': None,
|
||||
'order_number': entitlement.order_number,
|
||||
'created': entitlement.created.strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
|
||||
|
||||
@@ -17,6 +17,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from student.models import CourseEnrollment
|
||||
@@ -27,7 +28,7 @@ log = logging.getLogger(__name__)
|
||||
# Entitlements is not in CMS' INSTALLED_APPS so these imports will error during test collection
|
||||
if settings.ROOT_URLCONF == 'lms.urls':
|
||||
from entitlements.tests.factories import CourseEntitlementFactory
|
||||
from entitlements.models import CourseEntitlement, CourseEntitlementPolicy
|
||||
from entitlements.models import CourseEntitlement, CourseEntitlementPolicy, CourseEntitlementSupportDetail
|
||||
from entitlements.api.v1.serializers import CourseEntitlementSerializer
|
||||
from entitlements.api.v1.views import set_entitlement_policy
|
||||
|
||||
@@ -606,7 +607,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase):
|
||||
'support_details': [
|
||||
{
|
||||
'unenrolled_run': str(enrollment.course.id),
|
||||
'action': 'REISSUE',
|
||||
'action': CourseEntitlementSupportDetail.REISSUE,
|
||||
'comments': 'Severe illness.'
|
||||
}
|
||||
]
|
||||
@@ -625,6 +626,74 @@ class EntitlementViewSetTest(ModuleStoreTestCase):
|
||||
)
|
||||
assert results == CourseEntitlementSerializer(reinstated_entitlement).data
|
||||
|
||||
def test_reinstate_refundable_entitlement(self):
|
||||
""" Verify that an entitlement that is refundable stays refundable when support reinstates it. """
|
||||
enrollment = CourseEnrollmentFactory(user=self.user, is_active=True, course=CourseOverviewFactory(start=now()))
|
||||
fulfilled_entitlement = CourseEntitlementFactory.create(
|
||||
user=self.user, enrollment_course_run=enrollment
|
||||
)
|
||||
assert fulfilled_entitlement.is_entitlement_refundable() is True
|
||||
url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(fulfilled_entitlement.uuid)])
|
||||
|
||||
update_data = {
|
||||
'expired_at': None,
|
||||
'enrollment_course_run': None,
|
||||
'support_details': [
|
||||
{
|
||||
'unenrolled_run': str(enrollment.course.id),
|
||||
'action': CourseEntitlementSupportDetail.REISSUE,
|
||||
'comments': 'Severe illness.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
url,
|
||||
data=json.dumps(update_data),
|
||||
content_type='application/json'
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
reinstated_entitlement = CourseEntitlement.objects.get(
|
||||
uuid=fulfilled_entitlement.uuid
|
||||
)
|
||||
assert reinstated_entitlement.refund_locked is False
|
||||
assert reinstated_entitlement.is_entitlement_refundable() is True
|
||||
|
||||
def test_reinstate_unrefundable_entitlement(self):
|
||||
""" Verify that a no longer refundable entitlement does not become refundable when support reinstates it. """
|
||||
enrollment = CourseEnrollmentFactory(user=self.user, is_active=True)
|
||||
expired_entitlement = CourseEntitlementFactory.create(
|
||||
user=self.user, enrollment_course_run=enrollment, expired_at=datetime.now()
|
||||
)
|
||||
assert expired_entitlement.is_entitlement_refundable() is False
|
||||
url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(expired_entitlement.uuid)])
|
||||
|
||||
update_data = {
|
||||
'expired_at': None,
|
||||
'enrollment_course_run': None,
|
||||
'support_details': [
|
||||
{
|
||||
'unenrolled_run': str(enrollment.course.id),
|
||||
'action': CourseEntitlementSupportDetail.REISSUE,
|
||||
'comments': 'Severe illness.'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
url,
|
||||
data=json.dumps(update_data),
|
||||
content_type='application/json'
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
reinstated_entitlement = CourseEntitlement.objects.get(
|
||||
uuid=expired_entitlement.uuid
|
||||
)
|
||||
assert reinstated_entitlement.refund_locked is True
|
||||
assert reinstated_entitlement.is_entitlement_refundable() is False
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase):
|
||||
|
||||
@@ -275,6 +275,11 @@ class EntitlementViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
support_details = request.data.pop('support_details', [])
|
||||
|
||||
# If a patch request does not explicitly update an entitlement's refundability status, we want to ensure that
|
||||
# changes made to other attributes of the entitlement do not implicitly change its ability to be refunded.
|
||||
if request.data.get('refund_locked') is None:
|
||||
request.data['refund_locked'] = not entitlement.is_entitlement_refundable()
|
||||
|
||||
for support_detail in support_details:
|
||||
support_detail['entitlement'] = entitlement
|
||||
support_detail['support_user'] = request.user
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-12 12:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('entitlements', '0008_auto_20180328_1107'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='courseentitlement',
|
||||
name='refund_locked',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def backfill_refundability(apps, schema_editor):
|
||||
CourseEntitlementSupportDetail = apps.get_model('entitlements', 'CourseEntitlementSupportDetail')
|
||||
for support_detail in CourseEntitlementSupportDetail.objects.all().select_related('entitlement'):
|
||||
support_detail.entitlement.refund_locked = True
|
||||
support_detail.entitlement.save()
|
||||
|
||||
|
||||
def revert_backfill(apps, schema_editor):
|
||||
CourseEntitlementSupportDetail = apps.get_model('entitlements', 'CourseEntitlementSupportDetail')
|
||||
for support_detail in CourseEntitlementSupportDetail.objects.all().select_related('entitlement'):
|
||||
support_detail.entitlement.refund_locked = False
|
||||
support_detail.entitlement.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('entitlements', '0009_courseentitlement_refund_locked'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(backfill_refundability, revert_backfill),
|
||||
]
|
||||
@@ -162,6 +162,7 @@ class CourseEntitlement(TimeStampedModel):
|
||||
blank=True
|
||||
)
|
||||
order_number = models.CharField(max_length=128, null=True)
|
||||
refund_locked = models.BooleanField(default=False)
|
||||
_policy = models.ForeignKey(CourseEntitlementPolicy, null=True, blank=True)
|
||||
|
||||
@property
|
||||
@@ -226,7 +227,7 @@ class CourseEntitlement(TimeStampedModel):
|
||||
"""
|
||||
Returns a boolean as to whether or not the entitlement can be refunded based on the entitlement's policy
|
||||
"""
|
||||
return self.policy.is_entitlement_refundable(self)
|
||||
return not self.refund_locked and self.policy.is_entitlement_refundable(self)
|
||||
|
||||
def is_entitlement_redeemable(self):
|
||||
"""
|
||||
|
||||
@@ -25,6 +25,7 @@ const postEntitlement = ({ username, courseUuid, mode, action, comments = null }
|
||||
course_uuid: courseUuid,
|
||||
user: username,
|
||||
mode,
|
||||
refund_locked: true,
|
||||
support_details: [{
|
||||
action,
|
||||
comments,
|
||||
|
||||
Reference in New Issue
Block a user