Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion backend/api/grants/mutations.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
create_change_admin_log_entry,
)
from grants.models import Grant as GrantModel
from grants.tasks import get_name, notify_new_grant_reply_slack
from grants.tasks import (
create_and_send_grant_voucher,
get_name,
notify_new_grant_reply_slack,
)
from notifications.models import EmailTemplate, EmailTemplateIdentifier
from participants.models import Participant
from privacy_policy.record import record_privacy_policy_acceptance
Expand Down Expand Up @@ -352,4 +356,7 @@ def send_grant_reply(
admin_url = request.build_absolute_uri(grant.get_admin_url())
notify_new_grant_reply_slack.delay(grant_id=grant.id, admin_url=admin_url)

if grant.status == GrantModel.Status.confirmed:
create_and_send_grant_voucher.delay(grant_id=grant.id)

return Grant.from_model(grant)
26 changes: 26 additions & 0 deletions backend/api/grants/tests/test_send_grant_reply.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,29 @@ def test_call_notify_new_grant_reply(graphql_client, user, mocker):

assert response["data"]["sendGrantReply"]["__typename"] == "Grant"
mock_publisher.delay.assert_called_once_with(grant_id=grant.id, admin_url=ANY)


def test_create_voucher_when_grant_is_confirmed(graphql_client, user, mocker):
graphql_client.force_login(user)
grant = GrantFactory(user_id=user.id, status=Grant.Status.waiting_for_confirmation)
mock_voucher_task = mocker.patch(
"api.grants.mutations.create_and_send_grant_voucher"
)

response = _send_grant_reply(graphql_client, grant, status="confirmed")

assert response["data"]["sendGrantReply"]["__typename"] == "Grant"
mock_voucher_task.delay.assert_called_once_with(grant_id=grant.id)


def test_voucher_not_created_when_grant_is_refused(graphql_client, user, mocker):
graphql_client.force_login(user)
grant = GrantFactory(user_id=user.id, status=Grant.Status.waiting_for_confirmation)
mock_voucher_task = mocker.patch(
"api.grants.mutations.create_and_send_grant_voucher"
)

response = _send_grant_reply(graphql_client, grant, status="refused")

assert response["data"]["sendGrantReply"]["__typename"] == "Grant"
mock_voucher_task.delay.assert_not_called()
47 changes: 47 additions & 0 deletions backend/grants/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from django.conf import settings
from django.utils import timezone

from conferences.models.conference_voucher import ConferenceVoucher
from conferences.tasks import send_conference_voucher_email
from conferences.vouchers import create_conference_voucher
from grants.models import Grant
from integrations import slack
from notifications.models import EmailTemplate, EmailTemplateIdentifier
Expand Down Expand Up @@ -182,3 +185,47 @@ def _new_send_grant_email(

grant.applicant_reply_sent_at = timezone.now()
grant.save()


@app.task
def create_and_send_grant_voucher(grant_id: int):
"""
Creates a voucher for a confirmed grant and sends an email to the grantee.
This is triggered when a grant is confirmed by the user.
"""
grant = Grant.objects.get(id=grant_id)
conference = grant.conference
user = grant.user

# Check if user already has a voucher for this conference
existing_voucher = (
ConferenceVoucher.objects.for_conference(conference).for_user(user).first()
)

if existing_voucher:
# If user has a co-speaker voucher, upgrade it to a grant voucher
if existing_voucher.voucher_type == ConferenceVoucher.VoucherType.CO_SPEAKER:
logger.info(
"User %s already has a co-speaker voucher for conference %s, "
"upgrading to grant voucher",
user.id,
conference.id,
)
existing_voucher.voucher_type = ConferenceVoucher.VoucherType.GRANT
existing_voucher.save(update_fields=["voucher_type"])
send_conference_voucher_email.delay(conference_voucher_id=existing_voucher.id)
else:
logger.info(
"User %s already has a voucher for conference %s, not creating a new one",
user.id,
conference.id,
)
return

conference_voucher = create_conference_voucher(
conference=conference,
user=user,
voucher_type=ConferenceVoucher.VoucherType.GRANT,
)

send_conference_voucher_email.delay(conference_voucher_id=conference_voucher.id)
107 changes: 107 additions & 0 deletions backend/grants/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from conferences.tests.factories import ConferenceFactory, DeadlineFactory
from grants.tasks import (
create_and_send_grant_voucher,
send_grant_reply_approved_email,
send_grant_reply_rejected_email,
send_grant_reply_waiting_list_email,
Expand Down Expand Up @@ -466,3 +467,109 @@ def test_send_grant_waiting_list_email_missing_deadline():

with pytest.raises(ValueError, match="missing grants_waiting_list_update deadline"):
send_grant_reply_waiting_list_email(grant_id=grant.id)


def test_create_and_send_grant_voucher(mocker, sent_emails):
from conferences.models.conference_voucher import ConferenceVoucher
from notifications.models import EmailTemplateIdentifier
from notifications.tests.factories import EmailTemplateFactory

mock_create_voucher = mocker.patch(
"grants.tasks.create_conference_voucher",
)
mock_conference_voucher = mocker.MagicMock()
mock_conference_voucher.id = 123
mock_create_voucher.return_value = mock_conference_voucher

mock_send_email = mocker.patch(
"grants.tasks.send_conference_voucher_email",
)

user = UserFactory(
full_name="Marco Acierno",
email="marco@placeholder.it",
)
grant = GrantFactory(user=user)

EmailTemplateFactory(
conference=grant.conference,
identifier=EmailTemplateIdentifier.voucher_code,
)

create_and_send_grant_voucher(grant_id=grant.id)

mock_create_voucher.assert_called_once_with(
conference=grant.conference,
user=user,
voucher_type=ConferenceVoucher.VoucherType.GRANT,
)
mock_send_email.delay.assert_called_once_with(conference_voucher_id=123)


def test_create_and_send_grant_voucher_user_already_has_voucher(mocker):
from conferences.models.conference_voucher import ConferenceVoucher
from conferences.tests.factories import ConferenceVoucherFactory

mock_create_voucher = mocker.patch(
"grants.tasks.create_conference_voucher",
)
mock_send_email = mocker.patch(
"grants.tasks.send_conference_voucher_email",
)

user = UserFactory(
full_name="Marco Acierno",
email="marco@placeholder.it",
)
grant = GrantFactory(user=user)

# Create an existing voucher for this user and conference
ConferenceVoucherFactory(
conference=grant.conference,
user=user,
voucher_type=ConferenceVoucher.VoucherType.SPEAKER,
)

create_and_send_grant_voucher(grant_id=grant.id)

# Should not create a new voucher
mock_create_voucher.assert_not_called()
mock_send_email.delay.assert_not_called()


def test_create_and_send_grant_voucher_upgrades_co_speaker_voucher(mocker):
from conferences.models.conference_voucher import ConferenceVoucher
from conferences.tests.factories import ConferenceVoucherFactory

mock_create_voucher = mocker.patch(
"grants.tasks.create_conference_voucher",
)
mock_send_email = mocker.patch(
"grants.tasks.send_conference_voucher_email",
)

user = UserFactory(
full_name="Marco Acierno",
email="marco@placeholder.it",
)
grant = GrantFactory(user=user)

# Create an existing co-speaker voucher for this user and conference
existing_voucher = ConferenceVoucherFactory(
conference=grant.conference,
user=user,
voucher_type=ConferenceVoucher.VoucherType.CO_SPEAKER,
)

create_and_send_grant_voucher(grant_id=grant.id)

# Should not create a new voucher but should upgrade the existing one
mock_create_voucher.assert_not_called()

existing_voucher.refresh_from_db()
assert existing_voucher.voucher_type == ConferenceVoucher.VoucherType.GRANT

# Should send email with the upgraded voucher
mock_send_email.delay.assert_called_once_with(
conference_voucher_id=existing_voucher.id
)
Loading