Skip to content

Commit

Permalink
feat: setup signal to consume transaction reveral events
Browse files Browse the repository at this point in the history
  • Loading branch information
iloveagent57 committed Jun 26, 2024
1 parent 1c20396 commit 5590822
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 1 deletion.
9 changes: 8 additions & 1 deletion enterprise_access/apps/content_assignments/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ class LearnerContentAssignmentStateChoices:
CANCELLED = 'cancelled'
ERRORED = 'errored'
EXPIRED = 'expired'
REVERSED = 'reversed'

CHOICES = (
(ALLOCATED, 'Allocated'),
(ACCEPTED, 'Accepted'),
(CANCELLED, 'Cancelled'),
(ERRORED, 'Errored'),
(EXPIRED, 'Expired'),
(REVERSED, 'Reversed'),
)

# States which allow reallocation by an admin.
REALLOCATE_STATES = (CANCELLED, ERRORED, EXPIRED)
REALLOCATE_STATES = (CANCELLED, ERRORED, EXPIRED, REVERSED)

# States which allow cancellation by an admin.
CANCELABLE_STATES = (ALLOCATED, ERRORED)
Expand All @@ -33,6 +35,9 @@ class LearnerContentAssignmentStateChoices:
# States from which an assignment can be expired
EXPIRABLE_STATES = (ALLOCATED,)

# States from which an assignment can be reversed
REVERSIBLE_STATES = (ACCEPTED,)


class AssignmentActions:
"""
Expand All @@ -46,6 +51,7 @@ class AssignmentActions:
CANCELLED_ACKNOWLEDGED = 'cancelled_acknowledged'
EXPIRED = 'expired'
EXPIRED_ACKNOWLEDGED = 'expired_acknowledged'
REVERSED = 'reversed'

CHOICES = (
(LEARNER_LINKED, 'Learner linked to customer'),
Expand All @@ -56,6 +62,7 @@ class AssignmentActions:
(CANCELLED_ACKNOWLEDGED, 'Learner assignment cancellation acknowledged by learner'),
(EXPIRED, 'Learner assignment expired'),
(EXPIRED_ACKNOWLEDGED, 'Learner assignment expiration acknowledged by learner'),
(REVERSED, 'Transaction for this assignment has been reversed'),
)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 4.2.13 on 2024-06-26 19:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('content_assignments', '0019_learnercontentassignment_preferred_course_run_key'),
]

operations = [
migrations.AddField(
model_name='historicallearnercontentassignment',
name='reversed_at',
field=models.DateTimeField(blank=True, help_text='The last time this assignment was reversed. Null means the assignment is not currently reversed.', null=True),
),
migrations.AddField(
model_name='learnercontentassignment',
name='reversed_at',
field=models.DateTimeField(blank=True, help_text='The last time this assignment was reversed. Null means the assignment is not currently reversed.', null=True),
),
migrations.AlterField(
model_name='historicallearnercontentassignment',
name='state',
field=models.CharField(choices=[('allocated', 'Allocated'), ('accepted', 'Accepted'), ('cancelled', 'Cancelled'), ('errored', 'Errored'), ('expired', 'Expired'), ('reversed', 'Reversed')], db_index=True, default='allocated', help_text="The current state of the LearnerContentAssignment. One of: ['allocated', 'accepted', 'cancelled', 'errored', 'expired', 'reversed']", max_length=255),
),
migrations.AlterField(
model_name='historicallearnercontentassignmentaction',
name='action_type',
field=models.CharField(choices=[('learner_linked', 'Learner linked to customer'), ('notified', 'Learner notified of assignment'), ('reminded', 'Learner reminded about assignment'), ('redeemed', 'Learner redeemed the assigned content'), ('cancelled', 'Learner assignment cancelled'), ('cancelled_acknowledged', 'Learner assignment cancellation acknowledged by learner'), ('expired', 'Learner assignment expired'), ('expired_acknowledged', 'Learner assignment expiration acknowledged by learner'), ('reversed', 'Transaction for this assignment has been reversed')], db_index=True, help_text='The type of action take on the related assignment record.', max_length=255),
),
migrations.AlterField(
model_name='learnercontentassignment',
name='state',
field=models.CharField(choices=[('allocated', 'Allocated'), ('accepted', 'Accepted'), ('cancelled', 'Cancelled'), ('errored', 'Errored'), ('expired', 'Expired'), ('reversed', 'Reversed')], db_index=True, default='allocated', help_text="The current state of the LearnerContentAssignment. One of: ['allocated', 'accepted', 'cancelled', 'errored', 'expired', 'reversed']", max_length=255),
),
migrations.AlterField(
model_name='learnercontentassignmentaction',
name='action_type',
field=models.CharField(choices=[('learner_linked', 'Learner linked to customer'), ('notified', 'Learner notified of assignment'), ('reminded', 'Learner reminded about assignment'), ('redeemed', 'Learner redeemed the assigned content'), ('cancelled', 'Learner assignment cancelled'), ('cancelled_acknowledged', 'Learner assignment cancellation acknowledged by learner'), ('expired', 'Learner assignment expired'), ('expired_acknowledged', 'Learner assignment expiration acknowledged by learner'), ('reversed', 'Transaction for this assignment has been reversed')], db_index=True, help_text='The type of action take on the related assignment record.', max_length=255),
),
]
14 changes: 14 additions & 0 deletions enterprise_access/apps/content_assignments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ class Meta:
blank=True,
help_text="The last time this assignment was in an error state. Null means the assignment is not errored.",
)
reversed_at = models.DateTimeField(
null=True,
blank=True,
help_text="The last time this assignment was reversed. Null means the assignment is not currently reversed.",
)
transaction_uuid = models.UUIDField(
blank=True,
null=True,
Expand Down Expand Up @@ -685,6 +690,15 @@ def add_successful_acknowledged_expired_action(self):
completed_at=timezone.now(),
)

def add_successful_reversal_action(self):
"""
Adds a successful 'reversed' action for this assignment record.
"""
return self.actions.create(
action_type=AssignmentActions.REVERSED,
completed_at=timezone.now(),
)

@staticmethod
def _unique_retired_email():
"""
Expand Down
32 changes: 32 additions & 0 deletions enterprise_access/apps/content_assignments/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from openedx_events.enterprise.signals import LEDGER_TRANSACTION_REVERSED

from enterprise_access.apps.content_assignments.constants import LearnerContentAssignmentStateChoices
from enterprise_access.apps.content_assignments.models import LearnerContentAssignment
from enterprise_access.apps.core.models import User

Expand Down Expand Up @@ -34,3 +37,32 @@ def update_assignment_lms_user_id_from_user_email(sender, **kwargs): # pylint:
logger.info(
f'Set lms_user_id={user.lms_user_id} on {num_assignments_updated} assignments for User.id={user.id}'
)


@receiver(LEDGER_TRANSACTION_REVERSED)
def update_assignment_status_for_reversed_transaction(**kwargs):
"""
OEP-49 event handler to update assignment status for reversed transaction.
"""
ledger_transaction = kwargs.get('ledger_transaction')
transaction_uuid = ledger_transaction.uuid

try:
assignment_to_update = LearnerContentAssignment.objects.get(transaction_uuid=transaction_uuid)
except SubsidyAccessPolicy.DoesNotExist:
logger.error(f'No LearnerContentAssignment exists with transaction uuid: {transaction_uuid}')
return

if assignment_to_update.state in LearnerContentAssignmentStateChoices.REVERSIBLE_STATES:
assignment_to_update.state = LearnerContentAssignmentStateChoices.REVERSED
assignment_to_update.reversed_at = timezone.now()
assignment_to_update.save()
assignment_to_update.add_successful_reversal_action()
logger.info(
f'LearnerContentAssignment {assignment_to_update.uuid} reversed.'
)
else:
logger.warning(
f'Cannot reverse LearnerContentAssignment {assignment_to_update.uuid} '
f'because its state is {assignment_to_update.state}'
)

0 comments on commit 5590822

Please sign in to comment.