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

Commit

Permalink
feat: mail mobile team for a mobile course change in publisher (#4014)
Browse files Browse the repository at this point in the history
* feat: mail mobile team for a mobile course change in publisher

This will fix any unknown change from publisher to a course having mobile seats.
After this fix mobile team will see mail and adjust price of the course on playstore or appstore.
In the longer run we want to replace this solution by changing the course price directly using mobile platform apis.

LEARNER-9377

* fix: fixed coverage issue
  • Loading branch information
jawad-khan authored Aug 17, 2023
1 parent e233314 commit b5a96f1
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 0 deletions.
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 = 'abc@example.com'
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

0 comments on commit b5a96f1

Please sign in to comment.