|
49 | 49 | from enterprise_access.apps.subsidy_access_policy.api import approve_learner_credit_request_via_policy |
50 | 50 | from enterprise_access.apps.subsidy_access_policy.exceptions import SubisidyAccessPolicyRequestApprovalError |
51 | 51 | from enterprise_access.apps.subsidy_access_policy.models import SubsidyAccessPolicy |
| 52 | +from enterprise_access.apps.subsidy_request import api as subsidy_request_api |
52 | 53 | from enterprise_access.apps.subsidy_request.constants import ( |
53 | 54 | REUSABLE_REQUEST_STATES, |
54 | 55 | LearnerCreditAdditionalActionStates, |
@@ -1093,27 +1094,79 @@ def cancel(self, request, *args, **kwargs): |
1093 | 1094 | @action(detail=False, url_path="remind", methods=["post"]) |
1094 | 1095 | def remind(self, request, *args, **kwargs): |
1095 | 1096 | """ |
1096 | | - Remind a Learner that their LearnerCreditRequest is Approved and waiting for their action. |
| 1097 | + Send reminders to a list of learners with associated ``LearnerCreditRequests`` |
| 1098 | + record by list of uuids. |
| 1099 | +
|
| 1100 | + This action is idempotent and will only send reminders for requests |
| 1101 | + that are in a valid, remindable state (e.g., 'APPROVED'). |
1097 | 1102 | """ |
1098 | 1103 | serializer = serializers.LearnerCreditRequestRemindSerializer(data=request.data) |
1099 | 1104 | serializer.is_valid(raise_exception=True) |
1100 | | - learner_credit_request = serializer.get_learner_credit_request() |
1101 | | - assignment = learner_credit_request.assignment |
1102 | | - |
1103 | | - action_instance = LearnerCreditRequestActions.create_action( |
1104 | | - learner_credit_request=learner_credit_request, |
1105 | | - recent_action=get_action_choice(LearnerCreditAdditionalActionStates.REMINDED), |
1106 | | - status=get_user_message_choice(LearnerCreditAdditionalActionStates.REMINDED), |
| 1105 | + request_uuids = serializer.validated_data['learner_credit_request_uuids'] |
| 1106 | + learner_credit_requests = self.get_queryset().select_related('assignment').filter( |
| 1107 | + uuid__in=request_uuids |
1107 | 1108 | ) |
1108 | 1109 |
|
| 1110 | + if len(learner_credit_requests) != len(set(request_uuids)): |
| 1111 | + return Response( |
| 1112 | + status=status.HTTP_404_NOT_FOUND |
| 1113 | + ) |
| 1114 | + |
1109 | 1115 | try: |
1110 | | - send_reminder_email_for_pending_learner_credit_request.delay(assignment.uuid) |
| 1116 | + response = subsidy_request_api.remind_learner_credit_requests(learner_credit_requests) |
| 1117 | + if response.get('non_remindable_requests'): |
| 1118 | + return Response( |
| 1119 | + status=status.HTTP_422_UNPROCESSABLE_ENTITY |
| 1120 | + ) |
1111 | 1121 | return Response(status=status.HTTP_200_OK) |
1112 | | - except Exception as exc: # pylint: disable=broad-except |
1113 | | - # Optionally log an errored action here if the task couldn't be queued |
1114 | | - action_instance.status = get_user_message_choice(LearnerCreditRequestActionErrorReasons.EMAIL_ERROR) |
1115 | | - action_instance.error_reason = str(exc) |
1116 | | - action_instance.save() |
| 1122 | + except Exception: # pylint: disable=broad-except |
| 1123 | + return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY) |
| 1124 | + |
| 1125 | + @permission_required( |
| 1126 | + constants.REQUESTS_ADMIN_ACCESS_PERMISSION, |
| 1127 | + fn=get_enterprise_uuid_from_query_params, |
| 1128 | + ) |
| 1129 | + @action(detail=False, url_path="remind-all", methods=["post"], pagination_class=None) |
| 1130 | + def remind_all(self, request, *args, **kwargs): |
| 1131 | + """ |
| 1132 | + Send reminders for all selected learner credit requests that are in a remindable state. |
| 1133 | +
|
| 1134 | + This endpoint respects the filters applied in the request (e.g., by policy_uuid), |
| 1135 | + allowing admins to send bulk reminders to a specific subset of requests. |
| 1136 | +
|
| 1137 | + ``` |
| 1138 | + Raises: |
| 1139 | + 404 if no remindable learner credit requests were found |
| 1140 | + 422 if any of the learner credit requests threw an error (not found or not remindable) |
| 1141 | + ``` |
| 1142 | + """ |
| 1143 | + serializer = serializers.LearnerCreditRequestRemindAllSerializer(data=request.data) |
| 1144 | + serializer.is_valid(raise_exception=True) |
| 1145 | + policy_uuid = serializer.validated_data['policy_uuid'] |
| 1146 | + |
| 1147 | + # A request is only remindable if it is in the 'APPROVED' state. |
| 1148 | + learner_credit_requests = self.get_queryset().filter( |
| 1149 | + state=SubsidyRequestStates.APPROVED, |
| 1150 | + learner_credit_request_config__learner_credit_config__uuid=policy_uuid |
| 1151 | + ) |
| 1152 | + |
| 1153 | + if not learner_credit_requests.exists(): |
| 1154 | + return Response(status=status.HTTP_404_NOT_FOUND) |
| 1155 | + |
| 1156 | + try: |
| 1157 | + response = subsidy_request_api.remind_learner_credit_requests(learner_credit_requests) |
| 1158 | + if non_remindable_requests := response.get('non_remindable_requests'): |
| 1159 | + # This is very unlikely to occur, because we filter down to only the remindable |
| 1160 | + # requests before calling `remind_learner_credit_requests()`, and that function |
| 1161 | + # only declares requests to be non-remindable if they are not |
| 1162 | + # in the set of remindable states. |
| 1163 | + logger.error( |
| 1164 | + 'There were non-remindable requests in remind-all: %s', |
| 1165 | + non_remindable_requests, |
| 1166 | + ) |
| 1167 | + return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY) |
| 1168 | + return Response(status=status.HTTP_202_ACCEPTED) |
| 1169 | + except Exception: # pylint: disable=broad-except |
1117 | 1170 | return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY) |
1118 | 1171 |
|
1119 | 1172 | @permission_required( |
|
0 commit comments