Skip to content

Fix: Implement bulk cancel_all feature#82

Merged
zwidekalanga merged 7 commits intoedx:mainfrom
vshaikismail-sonata:ismail-ENT-10856
Mar 17, 2026
Merged

Fix: Implement bulk cancel_all feature#82
zwidekalanga merged 7 commits intoedx:mainfrom
vshaikismail-sonata:ismail-ENT-10856

Conversation

@vshaikismail-sonata
Copy link
Copy Markdown
Contributor

Description:-
Added a cancel_all endpoint for bulk cancellation of approved learner credit requests, following the same pattern as the existing remind_all endpoint.

Changes:-
Added LearnerCreditRequestCancelAllSerializer in serializers/subsidy_requests.py and exported it in init.py
Added add_successful_cancelled_action() and add_errored_cancelled_action() methods in models.py
Added cancel_learner_credit_requests() function in api.py that filters APPROVED requests, cancels assignments via assignments_api, creates action records, and queues email tasks
Added cancel_all() view method with schema decorator in browse_and_request.py
Updated tasks.py to track successful/errored cancelled actions in the cancel email task

Tests:-
test_cancel_all_success - Successful bulk cancellation
test_cancel_all_non_cancelable_requests - Non-APPROVED requests return 422
test_cancel_all_no_requests_found - Invalid policy returns 404
test_cancel_all_403_for_learner - Learners get 403 (admin-only)
test_cancel_all_with_mixed_states - Mixed APPROVED/PENDING states

image

Copilot AI review requested due to automatic review settings February 27, 2026 13:23
@vshaikismail-sonata vshaikismail-sonata requested review from a team as code owners February 27, 2026 13:23
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements a bulk cancel_all endpoint for learner credit requests, following the pattern established by the existing remind_all endpoint. The feature allows administrators to cancel all approved learner credit requests for a given policy in a single operation.

Changes:

  • Added LearnerCreditRequestCancelAllSerializer for request validation
  • Added cancel_learner_credit_requests() API function to handle bulk cancellation logic
  • Added cancel_all() view method with admin-only permissions
  • Updated task error handling to create action records for failed email sends
  • Added comprehensive test coverage for success, failure, and edge case scenarios

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
enterprise_access/apps/api/serializers/subsidy_requests.py Added LearnerCreditRequestCancelAllSerializer to validate policy_uuid for bulk cancellation
enterprise_access/apps/api/serializers/__init__.py Exported the new serializer for use in views
enterprise_access/apps/subsidy_request/models.py Added add_successful_cancelled_action() and add_errored_cancelled_action() methods to create action records
enterprise_access/apps/subsidy_request/api.py Added cancel_learner_credit_requests() function to handle bulk cancellation with proper filtering and error handling
enterprise_access/apps/api/v1/views/browse_and_request.py Added cancel_all() view method with schema decorator, admin permission checks, and appropriate HTTP status codes
enterprise_access/apps/subsidy_request/tasks.py Updated cancel email task to track successful cancellation actions and error handling
enterprise_access/apps/api/v1/tests/test_browse_and_request_views.py Added 5 comprehensive test cases covering success, failures, permissions, and edge cases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/subsidy_request/tasks.py Outdated
# Verify request was NOT cancelled
approved_request.refresh_from_db()
assert approved_request.state == SubsidyRequestStates.APPROVED

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test for non-cancelable requests should verify that an error action record is created. The API code at lines 265-267 in api.py calls request.add_errored_cancelled_action() when assignments fail to cancel. This test should verify that these error action records were created, similar to how test_cancel_failed_assignment_cancellation verifies error actions for the single cancel endpoint (lines 3030-3038).

Suggested change
# Verify an error action record was created for the non-cancelable request
actions = LearnerCreditRequestActions.objects.filter(request=approved_request)
assert actions.count() == 1
error_action = actions.first()
assert error_action.error_reason is not None

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/subsidy_request/tasks.py Outdated
Comment thread enterprise_access/apps/subsidy_request/models.py Outdated
Comment on lines +254 to +256
# Cancel all assignments
cancel_response = assignments_api.cancel_assignments(assignments_to_cancel, False)

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel_assignments updates assignment state in bulk, but the subsequent request/action updates aren’t protected by any transaction/exception handling. If a later DB write fails, assignments may already be CANCELLED while requests remain APPROVED, and the endpoint will 500. Consider wrapping the state transitions in transaction.atomic() (potentially per-request) and converting failures into non_cancelable entries with an errored action record instead of aborting the batch.

Copilot uses AI. Check for mistakes.
Comment thread enterprise_access/apps/subsidy_request/api.py Outdated
Comment thread enterprise_access/apps/api/v1/views/browse_and_request.py
Comment thread enterprise_access/apps/api/v1/views/browse_and_request.py Outdated
Comment thread enterprise_access/apps/subsidy_request/api.py Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/api/v1/views/browse_and_request.py Outdated
Comment thread enterprise_access/apps/subsidy_request/api.py Outdated
Copilot AI review requested due to automatic review settings March 2, 2026 06:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/subsidy_request/api.py Outdated
Comment on lines +1268 to +1269
return Response(
{"detail": "Policy not found."},
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authorization uses enterprise_customer_uuid from the request body, but this action loads policy_uuid directly. That allows an admin for enterprise A to submit their own enterprise UUID + a policy UUID from enterprise B and infer whether that policy exists based on the returned 404 detail (information disclosure). Consider validating request enterprise UUID matches policy.enterprise_customer_uuid (403/404 on mismatch) or deriving the enterprise UUID from the policy UUID inside the permission check.

Suggested change
return Response(
{"detail": "Policy not found."},
# Return the same generic 404 message as when no APPROVED requests are found
# to avoid leaking whether the policy exists.
return Response(
{"detail": "No APPROVED requests found for the given policy."},

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +285
# Collect assignments to cancel
assignments_to_cancel = [req.assignment for req in cancelable]

# Cancel all assignments
cancel_response = assignments_api.cancel_assignments(assignments_to_cancel, False)

actions_to_create = []

if cancel_response.get('non_cancelable'):
# If any assignments failed to cancel, mark those requests as non_cancelable
non_cancelable_assignment_ids = {a.uuid for a in cancel_response['non_cancelable']}
actually_cancelable = []
for request in cancelable:
if request.assignment.uuid in non_cancelable_assignment_ids:
non_cancelable.append(request)
# Add error action for non-cancelable (assignment cancellation failure)
actions_to_create.append(
LearnerCreditRequestActions(
learner_credit_request=request,
recent_action=get_action_choice(SubsidyRequestStates.CANCELLED),
status=get_user_message_choice(SubsidyRequestStates.APPROVED),
error_reason=get_error_reason_choice(
LearnerCreditRequestActionErrorReasons.FAILED_CANCELLATION
),
traceback=f"Failed to cancel assignment {request.assignment.uuid}",
)
)
else:
actually_cancelable.append(request)
cancelable = actually_cancelable
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel_assignments() is called before the DB transaction that updates LearnerCreditRequests/actions. If a later DB error occurs, assignments could be CANCELLED while requests remain APPROVED. Consider wrapping the assignment cancellation + request state updates + action creation in a single transaction.atomic() (mirroring the single-request cancel endpoint) to avoid partial state changes.

Suggested change
# Collect assignments to cancel
assignments_to_cancel = [req.assignment for req in cancelable]
# Cancel all assignments
cancel_response = assignments_api.cancel_assignments(assignments_to_cancel, False)
actions_to_create = []
if cancel_response.get('non_cancelable'):
# If any assignments failed to cancel, mark those requests as non_cancelable
non_cancelable_assignment_ids = {a.uuid for a in cancel_response['non_cancelable']}
actually_cancelable = []
for request in cancelable:
if request.assignment.uuid in non_cancelable_assignment_ids:
non_cancelable.append(request)
# Add error action for non-cancelable (assignment cancellation failure)
actions_to_create.append(
LearnerCreditRequestActions(
learner_credit_request=request,
recent_action=get_action_choice(SubsidyRequestStates.CANCELLED),
status=get_user_message_choice(SubsidyRequestStates.APPROVED),
error_reason=get_error_reason_choice(
LearnerCreditRequestActionErrorReasons.FAILED_CANCELLATION
),
traceback=f"Failed to cancel assignment {request.assignment.uuid}",
)
)
else:
actually_cancelable.append(request)
cancelable = actually_cancelable
# Perform assignment cancellation and related request/action handling atomically
with transaction.atomic():
# Collect assignments to cancel
assignments_to_cancel = [req.assignment for req in cancelable]
# Cancel all assignments
cancel_response = assignments_api.cancel_assignments(assignments_to_cancel, False)
actions_to_create = []
if cancel_response.get('non_cancelable'):
# If any assignments failed to cancel, mark those requests as non_cancelable
non_cancelable_assignment_ids = {a.uuid for a in cancel_response['non_cancelable']}
actually_cancelable = []
for request in cancelable:
if request.assignment.uuid in non_cancelable_assignment_ids:
non_cancelable.append(request)
# Add error action for non-cancelable (assignment cancellation failure)
actions_to_create.append(
LearnerCreditRequestActions(
learner_credit_request=request,
recent_action=get_action_choice(SubsidyRequestStates.CANCELLED),
status=get_user_message_choice(SubsidyRequestStates.APPROVED),
error_reason=get_error_reason_choice(
LearnerCreditRequestActionErrorReasons.FAILED_CANCELLATION
),
traceback=f"Failed to cancel assignment {request.assignment.uuid}",
)
)
else:
actually_cancelable.append(request)
cancelable = actually_cancelable

Copilot uses AI. Check for mistakes.
# Use transaction to ensure atomic updates and prevent partial failures
with transaction.atomic():
# Bulk update all cancelable requests
LearnerCreditRequest.objects.bulk_update(
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses LearnerCreditRequest.objects.bulk_update(...), but the model defines LearnerCreditRequest.bulk_update(...) to update modified and persist simple-history during bulk updates. Using the manager method bypasses that. Please switch to the model helper (or reuse _update_and_refresh_requests) so cancellations are audited consistently with approvals/declines.

Suggested change
LearnerCreditRequest.objects.bulk_update(
LearnerCreditRequest.bulk_update(

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you address this as its important.

Comment on lines +289 to +292
if actions_to_create:
with transaction.atomic():
LearnerCreditRequestActions.objects.bulk_create(actions_to_create)
return {
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action records are created via LearnerCreditRequestActions.objects.bulk_create(...), which skips the model’s LearnerCreditRequestActions.bulk_create(...) helper that records simple-history for bulk creates. Please use LearnerCreditRequestActions.bulk_create(...) here to keep the audit trail consistent.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread enterprise_access/apps/api/serializers/subsidy_requests.py
Comment thread enterprise_access/apps/api/v1/views/browse_and_request.py
policy_uuid = serializer.validated_data['policy_uuid']

# Get the policy and derive enterprise_customer_uuid from it for authorization
from enterprise_access.apps.subsidy_access_policy.models import SubsidyAccessPolicy
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SubsidyAccessPolicy is already imported at module scope in this file, so the import inside the method is redundant and can be removed to avoid duplicate imports and keep import style consistent.

Suggested change
from enterprise_access.apps.subsidy_access_policy.models import SubsidyAccessPolicy

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +95 to +101
from enterprise_access.apps.subsidy_request.constants import (
LearnerCreditRequestActionErrorReasons,
SubsidyRequestStates
)
from enterprise_access.apps.subsidy_request.utils import get_error_reason_choice, get_user_message_choice
from enterprise_access.utils import format_traceback

Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports inside log_errored_action will likely trip pylint's import-outside-toplevel (C0415) in the quality tox env. Prefer moving these imports to module scope (and reusing the existing SubsidyRequestStates import) or explicitly disabling import-outside-toplevel if there is a real circular dependency to avoid.

Copilot uses AI. Check for mistakes.
Comment thread enterprise_access/apps/api/serializers/subsidy_requests.py
Comment thread enterprise_access/apps/api/serializers/subsidy_requests.py Outdated
Comment thread enterprise_access/apps/api/v1/views/browse_and_request.py
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1262 to +1271
# Get the policy and derive enterprise_customer_uuid from it for authorization
from enterprise_access.apps.subsidy_access_policy.models import SubsidyAccessPolicy
try:
policy = SubsidyAccessPolicy.objects.get(uuid=policy_uuid)
enterprise_customer_uuid = policy.enterprise_customer_uuid
except SubsidyAccessPolicy.DoesNotExist:
return Response(
{"detail": "Policy not found."},
status=status.HTTP_404_NOT_FOUND
)
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel_all is protected by permission_required(..., fn=get_enterprise_uuid_from_request_data), which authorizes based on the caller-supplied enterprise_customer_uuid. The method then looks up the policy by policy_uuid and returns a distinct 404 message when the policy exists vs. doesn’t exist, enabling policy UUID enumeration across enterprises for any authorized admin of a different enterprise. Consider deriving the enterprise UUID for RBAC from the policy (e.g., use a permission fn based on policy_uuid, or validate enterprise_customer_uuid matches policy.enterprise_customer_uuid and return a generic 404 on mismatch).

Copilot uses AI. Check for mistakes.
# Use transaction to ensure atomic updates and prevent partial failures
with transaction.atomic():
# Bulk update all cancelable requests
LearnerCreditRequest.objects.bulk_update(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you address this as its important.

self.reviewed_at = localized_utcnow()
self.save()

def add_successful_cancelled_action(self):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method here is not used anywhere

policy_uuid = serializer.validated_data['policy_uuid']

# Get the policy and derive enterprise_customer_uuid from it for authorization
from enterprise_access.apps.subsidy_access_policy.models import SubsidyAccessPolicy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

Copilot AI review requested due to automatic review settings March 10, 2026 11:23
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +95 to +106
from enterprise_access.apps.subsidy_request.constants import (
LearnerCreditRequestActionErrorReasons,
SubsidyRequestStates
)
from enterprise_access.apps.subsidy_request.utils import get_error_reason_choice, get_user_message_choice
from enterprise_access.utils import format_traceback

learner_credit_request.add_errored_cancelled_action(
error_traceback=format_traceback(exc),
status=get_user_message_choice(SubsidyRequestStates.CANCELLED),
error_reason=get_error_reason_choice(LearnerCreditRequestActionErrorReasons.EMAIL_ERROR)
)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New imports inside log_errored_action will likely trigger pylint import-outside-toplevel (C0415) in the quality check. Since SubsidyRequestStates is already imported at module scope, prefer moving these imports to the top of the module (or add a targeted # pylint: disable=import-outside-toplevel with rationale if you need to avoid a circular import).

Copilot uses AI. Check for mistakes.
Comment on lines +3046 to +3086
self.set_jwt_cookie([{
'system_wide_role': SYSTEM_ENTERPRISE_ADMIN_ROLE,
'context': str(self.enterprise_customer_uuid_1)
}])

# Set up approved requests with assignments
assignment_1 = LearnerContentAssignmentFactory(
assignment_configuration=self.assignment_config,
content_quantity=-1000,
state='allocated'
)
assignment_2 = LearnerContentAssignmentFactory(
assignment_configuration=self.assignment_config,
content_quantity=-1000,
state='allocated'
)
request_1 = LearnerCreditRequestFactory(
enterprise_customer_uuid=self.enterprise_customer_uuid_1,
user=self.user,
learner_credit_request_config=self.learner_credit_config,
course_price=1000,
state=SubsidyRequestStates.APPROVED,
assignment=assignment_1
)
request_2 = LearnerCreditRequestFactory(
enterprise_customer_uuid=self.enterprise_customer_uuid_1,
user=self.user,
learner_credit_request_config=self.learner_credit_config,
course_price=1000,
state=SubsidyRequestStates.APPROVED,
assignment=assignment_2
)

# Mock successful assignment cancellations
mock_cancel_assignments.return_value = {'non_cancelable': []}

url = reverse('api:v1:learner-credit-requests-cancel-all')
data = {
'enterprise_customer_uuid': str(self.enterprise_customer_uuid_1),
'policy_uuid': str(self.policy.uuid)
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage for cancel_all doesn’t currently cover the enterprise/policy mismatch case (admin of enterprise A submits enterprise_customer_uuid=A but policy_uuid for enterprise B). Given the view’s current authorization flow, add a regression test that this scenario returns 403 to prevent cross-enterprise cancellation.

Copilot uses AI. Check for mistakes.
Comment thread enterprise_access/apps/api/v1/views/browse_and_request.py
Comment on lines +256 to +261
# Collect assignments to cancel
assignments_to_cancel = [req.assignment for req in cancelable]

# Cancel all assignments
cancel_response = assignments_api.cancel_assignments(assignments_to_cancel, False)

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel_assignments() is executed before the DB transaction that updates LearnerCreditRequest state/actions. If the subsequent bulk_update/bulk_create fails, assignments may remain cancelled while requests stay APPROVED, leaving data out of sync. Consider moving cancel_assignments(..., send_cancel_email_to_learner=False) inside the same transaction.atomic() block as the request updates/actions (or add compensating logic) to keep cancellation atomic.

Copilot uses AI. Check for mistakes.
Comment on lines +304 to +310
# Use transaction to ensure atomic updates and prevent partial failures
with transaction.atomic():
# Bulk update all cancelable requests
LearnerCreditRequest.objects.bulk_update(
cancelable,
['state', 'reviewer', 'reviewed_at']
)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses LearnerCreditRequest.objects.bulk_update(...), which bypasses the model’s LearnerCreditRequest.bulk_update(...) helper that updates modified and writes simple-history records. To preserve audit/history consistency (matching other bulk operations in this app), use LearnerCreditRequest.bulk_update(...) or the existing _update_and_refresh_requests(...) helper instead of the raw queryset bulk_update.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread enterprise_access/apps/subsidy_request/tasks.py
Comment thread enterprise_access/apps/subsidy_request/models.py
Comment thread enterprise_access/apps/subsidy_request/api.py

try:
policy = SubsidyAccessPolicy.objects.get(uuid=policy_uuid)
if str(policy.enterprise_customer_uuid) != str(enterprise_customer_uuid):
Copilot AI review requested due to automatic review settings March 17, 2026 06:30
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +543 to 565
def add_errored_cancelled_action(self, error_traceback, status=None, error_reason=None):
"""
Creates an action record for a failed cancellation with error details.

Args:
error_traceback: The error traceback string
status: The status to record (defaults to APPROVED for assignment cancellation failures)
error_reason: The error reason (defaults to FAILED_CANCELLATION)
"""
if status is None:
status = get_user_message_choice(SubsidyRequestStates.APPROVED)
if error_reason is None:
error_reason = get_error_reason_choice(LearnerCreditRequestActionErrorReasons.FAILED_CANCELLATION)

return LearnerCreditRequestActions.create_action(
learner_credit_request=self,
recent_action=get_action_choice(SubsidyRequestStates.CANCELLED),
status=status,
error_reason=error_reason,
traceback=error_traceback,
)

def add_successful_reminded_action(self):
Comment on lines +1270 to +1275
# Filter to APPROVED requests for this policy AND enterprise customer
learner_credit_requests = self.get_queryset().select_related('assignment').filter(
state=SubsidyRequestStates.APPROVED,
enterprise_customer_uuid=enterprise_customer_uuid,
learner_credit_request_config__learner_credit_config__uuid=policy_uuid
)
Copilot AI review requested due to automatic review settings March 17, 2026 07:02
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +543 to 565
def add_errored_cancelled_action(self, error_traceback, status=None, error_reason=None):
"""
Creates an action record for a failed cancellation with error details.

Args:
error_traceback: The error traceback string
status: The status to record (defaults to APPROVED for assignment cancellation failures)
error_reason: The error reason (defaults to FAILED_CANCELLATION)
"""
if status is None:
status = get_user_message_choice(SubsidyRequestStates.APPROVED)
if error_reason is None:
error_reason = get_error_reason_choice(LearnerCreditRequestActionErrorReasons.FAILED_CANCELLATION)

return LearnerCreditRequestActions.create_action(
learner_credit_request=self,
recent_action=get_action_choice(SubsidyRequestStates.CANCELLED),
status=status,
error_reason=error_reason,
traceback=error_traceback,
)

def add_successful_reminded_action(self):
@zwidekalanga zwidekalanga merged commit e984275 into edx:main Mar 17, 2026
3 checks passed
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 17, 2026

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants