diff --git a/ecommerce/enterprise/management/commands/send_enterprise_offer_limit_emails.py b/ecommerce/enterprise/management/commands/send_enterprise_offer_limit_emails.py index a5746ff2bfb..44caf882810 100644 --- a/ecommerce/enterprise/management/commands/send_enterprise_offer_limit_emails.py +++ b/ecommerce/enterprise/management/commands/send_enterprise_offer_limit_emails.py @@ -3,12 +3,14 @@ """ import logging from datetime import datetime +from urllib.parse import urljoin +from django.conf import settings +from django.contrib.sites.models import Site from django.core.management import BaseCommand -from django.db.models import Sum from ecommerce_worker.email.v1.api import send_offer_usage_email -from ecommerce.extensions.fulfillment.status import ORDER +from ecommerce.core.models import User from ecommerce.programs.custom import get_model ConditionalOffer = get_model('offer', 'ConditionalOffer') @@ -18,13 +20,6 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) -# pylint: disable=line-too-long -EMAIL_BODY = """ -You have used {percentage_usage}% of the {offer_type} Limit associated with the entitlement offer called "{offer_name}" -{offer_type}s Redeemed: {current_usage} -{offer_type}s Limit: {total_limit} -Please reach out to customersuccess@edx.org, or to your Account Manager or Customer Success representative, if you have any questions. -""" EMAIL_SUBJECT = 'Offer Usage Notification' @@ -62,35 +57,46 @@ def get_enrollment_limits(offer): return int(offer.max_global_applications), percentage_usage, int(offer.num_orders) @staticmethod - def get_booking_limits(offer): + def get_booking_limits(site, offer): """ Return the total discount limit, percentage usage and current usage of booking limit. """ - total_used_discount_amount = OrderDiscount.objects.filter( - offer_id=offer.id, - order__status=ORDER.COMPLETE - ).aggregate(Sum('amount'))['amount__sum'] - total_used_discount_amount = total_used_discount_amount if total_used_discount_amount else 0 - - percentage_usage = int((total_used_discount_amount / offer.max_discount) * 100) - return int(offer.max_discount), percentage_usage, int(total_used_discount_amount) + api_client = site.siteconfiguration.oauth_api_client + enterprise_customer_uuid = offer.condition.enterprise_customer_uuid + offer_analytics_url = urljoin( + settings.ENTERPRISE_ANALYTICS_API_URL, + f'/enterprise/api/v1/enterprise/{enterprise_customer_uuid}/offers/{offer.id}/', + ) + response = api_client.get(offer_analytics_url) + response.raise_for_status() + offer_analytics = response.json() + if not offer_analytics: + raise Exception(f'No analytics for offer: {offer.id}') + + return ( + offer_analytics['max_discount'], + offer_analytics['percent_of_offer_spent'], + offer_analytics['amount_of_offer_spent'], + ) - def get_email_content(self, offer): + def get_email_content(self, site, offer): """ Return the appropriate email body and subject of given offer. """ is_enrollment_limit_offer = bool(offer.max_global_applications) - total_limit, percentage_usage, current_usage = self.get_enrollment_limits(offer) if is_enrollment_limit_offer \ - else self.get_booking_limits(offer) - - email_body = EMAIL_BODY.format( - percentage_usage=percentage_usage, - total_limit=total_limit if is_enrollment_limit_offer else "{}$".format(total_limit), - offer_type='Enrollment' if is_enrollment_limit_offer else 'Booking', - offer_name=offer.name, - current_usage=current_usage if is_enrollment_limit_offer else "{}$".format(current_usage), + total_limit, percentage_usage, current_usage = ( + self.get_enrollment_limits(offer) + if is_enrollment_limit_offer + else self.get_booking_limits(site, offer) ) - return email_body, EMAIL_SUBJECT + + return { + 'percent_usage': percentage_usage, + 'total_limit': total_limit if is_enrollment_limit_offer else "${}".format(total_limit), + 'offer_type': 'Enrollment' if is_enrollment_limit_offer else 'Booking', + 'offer_name': offer.name, + 'current_usage': current_usage if is_enrollment_limit_offer else "${}".format(current_usage), + } @staticmethod def _get_enterprise_offers(): @@ -103,7 +109,7 @@ def _get_enterprise_offers(): ).exclude(emails_for_usage_alert='') def handle(self, *args, **options): - send_enterprise_offer_count = 0 + successful_send_count = 0 enterprise_offers = self._get_enterprise_offers() total_enterprise_offers_count = enterprise_offers.count() logger.info('[Offer Usage Alert] Total count of enterprise offers is %s.', total_enterprise_offers_count) @@ -114,16 +120,32 @@ def handle(self, *args, **options): enterprise_offer.name, enterprise_offer.id ) - send_enterprise_offer_count += 1 - email_body, email_subject = self.get_email_content(enterprise_offer) - OfferUsageEmail.create_record(enterprise_offer, meta_data={ - 'email_body': email_body, - 'email_subject': email_subject, - 'email_addresses': enterprise_offer.emails_for_usage_alert - }) - send_offer_usage_email.delay(enterprise_offer.emails_for_usage_alert, email_subject, email_body) + site = Site.objects.get_current() + email_body = self.get_email_content(site, enterprise_offer) + + lms_user_ids_by_email = { + user_email: User.get_lms_user_attribute_using_email(site, user_email, attribute='id') + for user_email in enterprise_offer.emails_for_usage_alert.strip().split(',') + } + + task_result = send_offer_usage_email.delay( + lms_user_ids_by_email, + EMAIL_SUBJECT, + email_body, + ) + # Block until the task is done, since we're inside a management command + # and likely running from a job scheduler (ex. Jenkins). + # propagate=False means we won't re-raise (and exit this method) if any one task fails. + task_result.get(propagate=False) + if task_result.successful(): + successful_send_count += 1 + OfferUsageEmail.create_record(enterprise_offer, meta_data={ + 'email_body': email_body, + 'email_subject': EMAIL_SUBJECT, + 'email_addresses': enterprise_offer.emails_for_usage_alert + }) logger.info( - '[Offer Usage Alert] %s of %s added to the email sending queue.', + '[Offer Usage Alert] %s of %s offers with usage alerts configured had an email sent.', + successful_send_count, total_enterprise_offers_count, - send_enterprise_offer_count ) diff --git a/ecommerce/settings/base.py b/ecommerce/settings/base.py index 47d8ee520b6..af991f23270 100644 --- a/ecommerce/settings/base.py +++ b/ecommerce/settings/base.py @@ -670,6 +670,8 @@ ENTERPRISE_CATALOG_SERVICE_URL = 'http://localhost:18160/' +ENTERPRISE_ANALYTICS_API_URL = 'http://localhost:19001' + ENTERPRISE_LEARNER_PORTAL_HOSTNAME = os.environ.get('ENTERPRISE_LEARNER_PORTAL_HOSTNAME', 'localhost:8734') # Name for waffle switch to use for enabling enterprise features on runtime. diff --git a/ecommerce/settings/devstack.py b/ecommerce/settings/devstack.py index 120e81d5376..5ff76a3df9b 100644 --- a/ecommerce/settings/devstack.py +++ b/ecommerce/settings/devstack.py @@ -62,6 +62,8 @@ ENTERPRISE_CATALOG_API_URL = urljoin(f"{ENTERPRISE_CATALOG_SERVICE_URL}/", 'api/v1/') +ENTERPRISE_ANALYTICS_API_URL = 'http://edx.devstack.analyticsapi:19001' + # PAYMENT PROCESSING PAYMENT_PROCESSOR_CONFIG = { 'edx': {