From 0f62309e6c92d8df172b9a4b3f5d90c915b2b2bf Mon Sep 17 00:00:00 2001 From: Cyril Nxumalo <80963114+zwidekalanga@users.noreply.github.com> Date: Mon, 25 May 2026 09:16:05 +0200 Subject: [PATCH] feat: Make decline-all endpoint support decline reason --- .../apps/api/serializers/subsidy_requests.py | 6 +++++ .../v1/tests/test_browse_and_request_views.py | 26 +++++++++++++++++++ .../apps/api/v1/views/browse_and_request.py | 1 + 3 files changed, 33 insertions(+) diff --git a/enterprise_access/apps/api/serializers/subsidy_requests.py b/enterprise_access/apps/api/serializers/subsidy_requests.py index d9b45e22..931c4ca8 100644 --- a/enterprise_access/apps/api/serializers/subsidy_requests.py +++ b/enterprise_access/apps/api/serializers/subsidy_requests.py @@ -404,6 +404,12 @@ class LearnerCreditRequestDeclineAllSerializer(serializers.Serializer): # pylin required=True, help_text="The UUID of the SubsidyAccessPolicy to filter requests by." ) + decline_reason = serializers.CharField( + required=False, + allow_blank=True, + allow_null=True, + help_text="Reason for declining all matching requests.", + ) # pylint: disable=abstract-method diff --git a/enterprise_access/apps/api/v1/tests/test_browse_and_request_views.py b/enterprise_access/apps/api/v1/tests/test_browse_and_request_views.py index 82a27dce..ff126869 100644 --- a/enterprise_access/apps/api/v1/tests/test_browse_and_request_views.py +++ b/enterprise_access/apps/api/v1/tests/test_browse_and_request_views.py @@ -4625,6 +4625,32 @@ def test_decline_all_success(self, _mock_decline_notification_task): self.assertEqual(len(results['declined']), 5) self.assertEqual(len(results['non_declinable']), 0) + @mock.patch('enterprise_access.apps.subsidy_request.api.send_learner_credit_bnr_decline_notification_task') + def test_decline_all_forwards_decline_reason(self, _mock_decline_notification_task): + """Test decline_all persists decline_reason on each declined request.""" + self.set_jwt_cookie([{ + 'system_wide_role': SYSTEM_ENTERPRISE_ADMIN_ROLE, + 'context': str(self.enterprise_customer_uuid_1) + }]) + + response = self.client.post( + reverse('api:v1:learner-credit-requests-decline-all'), + data={ + 'policy_uuid': str(self.policy.uuid), + 'enterprise_customer_uuid': str(self.enterprise_customer_uuid_1), + 'decline_reason': 'Budget constraints for Q2', + }, + content_type='application/json', + ) + + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + self.user_request_1.refresh_from_db() + self.user_request_2.refresh_from_db() + self.enterprise_request.refresh_from_db() + self.assertEqual(self.user_request_1.decline_reason, 'Budget constraints for Q2') + self.assertEqual(self.user_request_2.decline_reason, 'Budget constraints for Q2') + self.assertEqual(self.enterprise_request.decline_reason, 'Budget constraints for Q2') + def test_decline_all_no_declinable_requests(self): """Test decline_all returns 404 when no declinable requests exist.""" self.set_jwt_cookie([{ diff --git a/enterprise_access/apps/api/v1/views/browse_and_request.py b/enterprise_access/apps/api/v1/views/browse_and_request.py index e50657f9..bc6c45b6 100644 --- a/enterprise_access/apps/api/v1/views/browse_and_request.py +++ b/enterprise_access/apps/api/v1/views/browse_and_request.py @@ -1442,6 +1442,7 @@ def decline_all(self, request, *args, **kwargs): result = subsidy_request_api.decline_learner_credit_requests( learner_credit_requests, reviewer=request.user, + reason=serializer.validated_data.get('decline_reason'), ) response_data = { 'declined': [str(r.uuid) for r in result.get('declined', [])],