From 01c1cad42ab5f8307a7fa81bd5309f4808efe8be Mon Sep 17 00:00:00 2001 From: Alexander Dusenbery Date: Wed, 14 Aug 2024 11:34:50 -0400 Subject: [PATCH] feat: [wip] can_redeem checks for enrollment deadline --- .../apps/api/v1/views/subsidy_access_policy.py | 2 ++ .../apps/subsidy_access_policy/constants.py | 3 +++ .../subsidy_access_policy/content_metadata_api.py | 9 +++++++++ .../apps/subsidy_access_policy/models.py | 13 ++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/enterprise_access/apps/api/v1/views/subsidy_access_policy.py b/enterprise_access/apps/api/v1/views/subsidy_access_policy.py index 5c901dfa..3bcd8747 100755 --- a/enterprise_access/apps/api/v1/views/subsidy_access_policy.py +++ b/enterprise_access/apps/api/v1/views/subsidy_access_policy.py @@ -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, @@ -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, diff --git a/enterprise_access/apps/subsidy_access_policy/constants.py b/enterprise_access/apps/subsidy_access_policy/constants.py index b4819891..f92c6adb 100644 --- a/enterprise_access/apps/subsidy_access_policy/constants.py +++ b/enterprise_access/apps/subsidy_access_policy/constants.py @@ -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 = \ @@ -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" diff --git a/enterprise_access/apps/subsidy_access_policy/content_metadata_api.py b/enterprise_access/apps/subsidy_access_policy/content_metadata_api.py index f2b24f5b..0dc12f43 100644 --- a/enterprise_access/apps/subsidy_access_policy/content_metadata_api.py +++ b/enterprise_access/apps/subsidy_access_policy/content_metadata_api.py @@ -7,6 +7,7 @@ import requests from django.conf import settings +from django.utils import parse_datetime from edx_django_utils.cache import TieredCache from requests.exceptions import HTTPError @@ -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_metdata): + """ + Helper to return a datetime object representing + the enrollment deadline for a content_metdata record. + """ + return parse_datetime(content_metadata['enroll_by_date']) diff --git a/enterprise_access/apps/subsidy_access_policy/models.py b/enterprise_access/apps/subsidy_access_policy/models.py index ce97a234..34d249fc 100644 --- a/enterprise_access/apps/subsidy_access_policy/models.py +++ b/enterprise_access/apps/subsidy_access_policy/models.py @@ -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, @@ -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, @@ -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 @@ -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. @@ -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: