Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

feat: mail mobile team for a mobile course change in publisher #4014

Merged
merged 3 commits into from
Aug 17, 2023
Merged
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
3 changes: 3 additions & 0 deletions docs/additional_features/gate_ecommerce.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ Waffle offers the following feature gates.
* - disable_redundant_payment_check_for_mobile
- Switch
- Enable returning an error for duplicate transaction_id for mobile in-app purchases.
* - mail_mobile_team_for_change_in_course
- Switch
- Alert mobile team for a change in a course having mobile seats, so that they can adjust prices on mobile platforms.
* - enable_stripe_payment_processor
- Flag
- Ignore client side payment processor setting and use Stripe. For background, see `frontend-app-payment 0005-stripe-custom-actions <https://github.com/openedx/frontend-app-payment/blob/master/docs/decisions/0005-stripe-custom-actions.rst>`_.
Expand Down
9 changes: 9 additions & 0 deletions ecommerce/extensions/api/constatnts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# .. toggle_name: mail_mobile_team_for_change_in_course
# .. toggle_type: waffle_switch
# .. toggle_default: False
# .. toggle_description: Alert mobile team for a change in a course having mobile seats.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2023-07-25
# .. toggle_tickets: LEARNER-9377
# .. toggle_status: supported
MAIL_MOBILE_TEAM_FOR_CHANGE_IN_COURSE = 'mail_mobile_team_for_change_in_course'
13 changes: 13 additions & 0 deletions ecommerce/extensions/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
get_enterprise_customer_uuid_from_voucher
)
from ecommerce.entitlements.utils import create_or_update_course_entitlement
from ecommerce.extensions.api.constatnts import MAIL_MOBILE_TEAM_FOR_CHANGE_IN_COURSE
from ecommerce.extensions.api.utils import send_mail_to_mobile_team_for_change_in_course
from ecommerce.extensions.api.v2.constants import (
ENABLE_HOIST_ORDER_HISTORY,
REFUND_ORDER_EMAIL_CLOSING,
Expand Down Expand Up @@ -820,6 +822,13 @@ def validate_products(self, products):

return products

def _get_seats_offered_on_mobile(self, course):
certificate_type_query = Q(attributes__name='certificate_type', attribute_values__value_text='verified')
mobile_query = Q(stockrecords__partner_sku__contains='mobile')
mobile_seats = course.seat_products.filter(certificate_type_query & mobile_query)

return mobile_seats

def get_partner(self):
"""Validate partner"""
if not self.partner:
Expand Down Expand Up @@ -879,6 +888,10 @@ def save(self): # pylint: disable=arguments-differ
published = (resp_message is None)

if published:
mobile_seats = self._get_seats_offered_on_mobile(course)
if waffle.switch_is_active(MAIL_MOBILE_TEAM_FOR_CHANGE_IN_COURSE) and mobile_seats:
send_mail_to_mobile_team_for_change_in_course(course, mobile_seats)

return created, None, None
raise Exception(resp_message)

Expand Down
62 changes: 62 additions & 0 deletions ecommerce/extensions/api/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import mock
from testfixtures import LogCapture

from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.api.utils import send_mail_to_mobile_team_for_change_in_course
from ecommerce.extensions.iap.models import IAPProcessorConfiguration
from ecommerce.tests.testcases import TestCase


class UtilTests(TestCase):
def setUp(self):
super(UtilTests, self).setUp()
self.course = CourseFactory(id='test/course/123', name='Test Course 123')
seat = self.course.create_or_update_seat('verified', True, 60)
second_seat = self.course.create_or_update_seat('verified', True, 70)
self.mock_mobile_team_mail = '[email protected]'
self.mock_email_body = {
'subject': 'Course Change Alert for Test Course 123',
'body': 'Course: Test Course 123, Sku: {}, Price: 70.00\n'
'Course: Test Course 123, Sku: {}, Price: 60.00'.format(
second_seat.stockrecords.all()[0].partner_sku,
seat.stockrecords.all()[0].partner_sku
)
}

def test_send_mail_to_mobile_team_with_no_email_specified(self):
logger_name = 'ecommerce.extensions.api.utils'
email_sender = 'ecommerce.extensions.communication.utils.Dispatcher.dispatch_direct_messages'
msg_t = "Couldn't mail mobile team for change in {}. No email was specified for mobile team in configurations"
msg = msg_t.format(self.course.name)
with LogCapture(logger_name) as utils_logger,\
mock.patch(email_sender) as mock_send_email:

send_mail_to_mobile_team_for_change_in_course(self.course, self.course.seat_products.all())
utils_logger.check_present(
(
logger_name,
'INFO',
msg
)
)
assert mock_send_email.call_count == 0

def test_send_mail_to_mobile_team(self):
logger_name = 'ecommerce.extensions.api.utils'
email_sender = 'ecommerce.extensions.communication.utils.Dispatcher.dispatch_direct_messages'
iap_configs = IAPProcessorConfiguration.get_solo()
iap_configs.mobile_team_email = self.mock_mobile_team_mail
iap_configs.save()
with LogCapture(logger_name) as utils_logger,\
mock.patch(email_sender) as mock_send_email:

send_mail_to_mobile_team_for_change_in_course(self.course, self.course.seat_products.all())
utils_logger.check_present(
(
logger_name,
'INFO',
"Sent change in {} email to mobile team.".format(self.course.name)
)
)
assert mock_send_email.call_count == 1
mock_send_email.assert_called_with(self.mock_mobile_team_mail, self.mock_email_body)
36 changes: 36 additions & 0 deletions ecommerce/extensions/api/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import logging

from oscar.core.loading import get_class

from ecommerce.extensions.iap.models import IAPProcessorConfiguration

Dispatcher = get_class('communication.utils', 'Dispatcher')
logger = logging.getLogger(__name__)


def send_mail_to_mobile_team_for_change_in_course(course, seats):
recipient = IAPProcessorConfiguration.get_solo().mobile_team_email
if not recipient:
msg = "Couldn't mail mobile team for change in %s. No email was specified for mobile team in configurations"
logger.info(msg, course.name)
return

def format_seat(seat):
seat_template = "Course: {}, Sku: {}, Price: {}"
stock_record = seat.stockrecords.all()[0]
result = seat_template.format(
course.name,
stock_record.partner_sku,
stock_record.price_excl_tax,
)
return result

formatted_seats = [format_seat(seat) for seat in seats if seat.stockrecords.all()]

messages = {
'subject': 'Course Change Alert for {}'.format(course.name),
'body': "\n".join(formatted_seats)
}

Dispatcher().dispatch_direct_messages(recipient, messages)
logger.info("Sent change in %s email to mobile team.", course.name)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.19 on 2023-08-02 08:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('iap', '0005_paymentprocessorresponseextension_meta_data'),
]

operations = [
migrations.AddField(
model_name='iapprocessorconfiguration',
name='mobile_team_email',
field=models.EmailField(default='', max_length=254, verbose_name='mobile team email'),
),
]
6 changes: 6 additions & 0 deletions ecommerce/extensions/iap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class IAPProcessorConfiguration(SingletonModel):
)
)

mobile_team_email = models.EmailField(
default='',
verbose_name=_('mobile team email'),
max_length=254
)

class Meta:
verbose_name = "IAP Processor Configuration"

Expand Down
Loading