Skip to content

Commit

Permalink
feat: can_redeem now checks for enrollment deadline
Browse files Browse the repository at this point in the history
ENT-7472
  • Loading branch information
iloveagent57 committed Aug 14, 2024
1 parent 87ff1cf commit 6ed0686
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 3 deletions.
2 changes: 2 additions & 0 deletions enterprise_access/apps/api/v1/views/subsidy_access_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from enterprise_access.apps.events.utils import send_subsidy_redemption_event_to_event_bus
from enterprise_access.apps.subsidy_access_policy.constants import (
GROUP_MEMBERS_WITH_AGGREGATES_DEFAULT_PAGE_SIZE,
REASON_BEYOND_ENROLLMENT_DEADLINE,
REASON_CONTENT_NOT_IN_CATALOG,
REASON_LEARNER_ASSIGNMENT_CANCELLED,
REASON_LEARNER_ASSIGNMENT_FAILED,
Expand Down Expand Up @@ -224,6 +225,7 @@ def _get_user_message_for_reason(reason_slug, enterprise_admin_users):
REASON_LEARNER_MAX_SPEND_REACHED: MissingSubsidyAccessReasonUserMessages.LEARNER_LIMITS_REACHED,
REASON_LEARNER_MAX_ENROLLMENTS_REACHED: MissingSubsidyAccessReasonUserMessages.LEARNER_LIMITS_REACHED,
REASON_CONTENT_NOT_IN_CATALOG: MissingSubsidyAccessReasonUserMessages.CONTENT_NOT_IN_CATALOG,
REASON_BEYOND_ENROLLMENT_DEADLINE: MissingSubsidyAccessReasonUserMessages.BEYOND_ENROLLMENT_DEADLINE,
REASON_LEARNER_NOT_ASSIGNED_CONTENT: MissingSubsidyAccessReasonUserMessages.LEARNER_NOT_ASSIGNED_CONTENT,
REASON_LEARNER_ASSIGNMENT_CANCELLED: MissingSubsidyAccessReasonUserMessages.LEARNER_ASSIGNMENT_CANCELED,
REASON_LEARNER_ASSIGNMENT_FAILED: MissingSubsidyAccessReasonUserMessages.LEARNER_NOT_ASSIGNED_CONTENT,
Expand Down
3 changes: 3 additions & 0 deletions enterprise_access/apps/subsidy_access_policy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class MissingSubsidyAccessReasonUserMessages:
LEARNER_LIMITS_REACHED = "You can't enroll right now because of limits set by your organization."
CONTENT_NOT_IN_CATALOG = \
"You can't enroll right now because this course is no longer available in your organization's catalog."
BEYOND_ENROLLMENT_DEADLINE = \
"You can't enroll right now because the enrollment deadline for this course has passed."
LEARNER_NOT_IN_ENTERPRISE = \
"You can't enroll right now because your account is no longer associated with the organization."
LEARNER_NOT_ASSIGNED_CONTENT = \
Expand All @@ -104,6 +106,7 @@ class MissingSubsidyAccessReasonUserMessages:
REASON_POLICY_EXPIRED = "policy_expired"
REASON_SUBSIDY_EXPIRED = "subsidy_expired"
REASON_CONTENT_NOT_IN_CATALOG = "content_not_in_catalog"
REASON_BEYOND_ENROLLMENT_DEADLINE = "beyond_enrollment_deadline"
REASON_LEARNER_NOT_IN_ENTERPRISE = "learner_not_in_enterprise"
REASON_LEARNER_NOT_IN_ENTERPRISE_GROUP = "learner_not_in_enterprise_group"
REASON_NOT_ENOUGH_VALUE_IN_SUBSIDY = "not_enough_value_in_subsidy"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import requests
from django.conf import settings
from django.utils.dateparse import parse_datetime
from edx_django_utils.cache import TieredCache
from requests.exceptions import HTTPError

Expand Down Expand Up @@ -125,3 +126,11 @@ def list_price_dict_from_usd_cents(list_price_integer_cents):
"usd": list_price_decimal_dollars,
"usd_cents": list_price_integer_cents,
}


def enroll_by_datetime(content_metadata):
"""
Helper to return a datetime object representing
the enrollment deadline for a content_metdata record.
"""
return parse_datetime(content_metadata['enroll_by_date'])
13 changes: 10 additions & 3 deletions enterprise_access/apps/subsidy_access_policy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from .constants import (
CREDIT_POLICY_TYPE_PRIORITY,
FORCE_ENROLLMENT_KEYWORD,
REASON_BEYOND_ENROLLMENT_DEADLINE,
REASON_CONTENT_NOT_IN_CATALOG,
REASON_LEARNER_ASSIGNMENT_CANCELLED,
REASON_LEARNER_ASSIGNMENT_EXPIRED,
Expand All @@ -46,6 +47,7 @@
TransactionStateChoices
)
from .content_metadata_api import (
enroll_by_datetime,
get_and_cache_catalog_contains_content,
get_and_cache_content_metadata,
get_list_price_for_content,
Expand Down Expand Up @@ -686,7 +688,7 @@ def _log_redeemability(self, is_redeemable, reason, lms_user_id, content_key, ex
)
logger.info(message, self.uuid, is_redeemable, reason, lms_user_id, content_key, extra)

def can_redeem(self, lms_user_id, content_key, skip_customer_user_check=False):
def can_redeem(self, lms_user_id, content_key, skip_customer_user_check=False, skip_enrollment_deadline_check=False):
"""
Check that a given learner can redeem the given content.
The ordering of each conditional is intentional based on an expected
Expand Down Expand Up @@ -732,7 +734,12 @@ def can_redeem(self, lms_user_id, content_key, skip_customer_user_check=False):
self._log_redeemability(False, REASON_CONTENT_NOT_IN_CATALOG, lms_user_id, content_key)
return (False, REASON_CONTENT_NOT_IN_CATALOG, [])

# TODO: Add Course Upgrade/Registration Deadline Passed Error here
# Check if the current time is beyond the enrollment deadline for the content
if not skip_enrollment_deadline_check:
enrollment_deadline = enroll_by_datetime(content_metadata)
if enrollment_deadline and (timezone.now() > enrollment_deadline):
self._log_redeemability(False, REASON_BEYOND_ENROLLMENT_DEADLINE, lms_user_id, content_key)
return (False, REASON_BEYOND_ENROLLMENT_DEADLINE, [])

# We want to wait to do these checks that might require a call
# to the enterprise-subsidy service until we *know* we'll need the data.
Expand Down Expand Up @@ -1731,7 +1738,7 @@ def force_redeem(self, extra_metadata=None):
try:
with self.subsidy_access_policy.lock():
can_redeem, reason, existing_transactions = self.subsidy_access_policy.can_redeem(
self.lms_user_id, self.course_run_key,
self.lms_user_id, self.course_run_key, skip_enrollment_deadline_check=True,
)
extra_metadata = extra_metadata or {}
if can_redeem:
Expand Down
29 changes: 29 additions & 0 deletions enterprise_access/apps/subsidy_access_policy/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from enterprise_access.apps.content_assignments.models import AssignmentConfiguration
from enterprise_access.apps.content_assignments.tests.factories import LearnerContentAssignmentFactory
from enterprise_access.apps.subsidy_access_policy.constants import (
REASON_BEYOND_ENROLLMENT_DEADLINE,
REASON_CONTENT_NOT_IN_CATALOG,
REASON_LEARNER_ASSIGNMENT_CANCELLED,
REASON_LEARNER_ASSIGNMENT_EXPIRED,
Expand Down Expand Up @@ -318,6 +319,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
expected_policy_can_redeem,
expect_content_metadata_fetch=True,
expect_transaction_fetch=True,
enroll_by_date='2099-01-01T00:00:00Z',
):
"""
Test the can_redeem method of PerLearnerEnrollmentCapLearnerCreditAccessPolicy model
Expand All @@ -327,6 +329,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
self.mock_catalog_contains_content_key.return_value = catalog_contains_content
self.mock_get_content_metadata.return_value = {
'content_price': 200,
'enroll_by_date': enroll_by_date,
}
self.mock_subsidy_client.can_redeem.return_value = subsidy_is_redeemable
self.mock_transactions_cache_for_learner.return_value = transactions_for_learner
Expand Down Expand Up @@ -369,6 +372,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: True
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {'total_quantity': -100}},
Expand All @@ -380,6 +384,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': False,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
Expand All @@ -388,11 +393,26 @@ def test_learner_enrollment_cap_policy_can_redeem(
'expect_content_metadata_fetch': False,
'expect_transaction_fetch': False,
},
{
# Content enrollment deadline has passed, every other check would succeed.
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2020-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
'transactions_for_policy': {'results': [], 'aggregates': {'total_quantity': -200}},
'expected_policy_can_redeem': (False, REASON_BEYOND_ENROLLMENT_DEADLINE, []),
'expect_content_metadata_fetch': True,
'expect_transaction_fetch': False,
},
{
# Learner is not in the enterprise, every other check would succeed.
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': None,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
Expand All @@ -406,6 +426,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD_NO_GROUPS,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
Expand All @@ -419,6 +440,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': False, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
Expand All @@ -431,6 +453,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {
Expand All @@ -451,6 +474,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {
Expand All @@ -470,6 +494,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': False,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': True},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
Expand All @@ -483,6 +508,7 @@ def test_learner_enrollment_cap_policy_can_redeem(
# Expected can_redeem result: False
'policy_is_active': True,
'catalog_contains_content': True,
'enroll_by_date': '2099-01-01T00:00:00Z',
'get_enterprise_user': TEST_USER_RECORD,
'subsidy_is_redeemable': {'can_redeem': True, 'active': False},
'transactions_for_learner': {'transactions': [], 'aggregates': {}},
Expand All @@ -495,6 +521,7 @@ def test_learner_spend_cap_policy_can_redeem(
self,
policy_is_active,
catalog_contains_content,
enroll_by_date,
get_enterprise_user,
subsidy_is_redeemable,
transactions_for_learner,
Expand All @@ -511,6 +538,7 @@ def test_learner_spend_cap_policy_can_redeem(
self.mock_catalog_contains_content_key.return_value = catalog_contains_content
self.mock_get_content_metadata.return_value = {
'content_price': 200,
'enroll_by_date': enroll_by_date,
}
self.mock_subsidy_client.can_redeem.return_value = subsidy_is_redeemable
self.mock_transactions_cache_for_learner.return_value = transactions_for_learner
Expand Down Expand Up @@ -1066,6 +1094,7 @@ def test_can_redeem(
self.mock_catalog_contains_content_key.return_value = True
self.mock_get_content_metadata.return_value = {
'content_price': 200,
'enroll_by_date': '2099-01-01T00:00:00Z',
}
self.mock_assign_get_content_metadata.return_value = {
'content_price': 200,
Expand Down

0 comments on commit 6ed0686

Please sign in to comment.