Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add send email command for case 1 #90

Open
wants to merge 12 commits into
base: and/temporal_payment_notifications
Choose a base branch
from
1 change: 1 addition & 0 deletions eox_nelp/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
from eox_nelp.admin.course_creators import * # noqa: F401
from eox_nelp.course_experience.admin import * # noqa: F401
from eox_nelp.notifications.admin import * # noqa: F401
from eox_nelp.payment_notifications.admin import * # noqa: F401
7 changes: 7 additions & 0 deletions eox_nelp/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class EoxNelpConfig(AppConfig):
'signal_path': 'openedx_events.learning.signals.CERTIFICATE_CREATED',
'dispatch_uid': 'certificate_publisher_receiver',
},
{
'receiver_func_name': 'update_payment_notifications',
'signal_path': 'django.db.models.signals.post_save',
'dispatch_uid': 'update_payment_notifications_receiver',
'sender_path': 'common.djangoapps.student.models.CourseEnrollment',
},
],
},
},
Expand All @@ -63,6 +69,7 @@ def ready(self):
# pylint: disable=unused-import, import-error, import-outside-toplevel
# This is required in order to register the receiver inside handlers module.
from cms.djangoapps.contentstore.signals import handlers # noqa: F401

run_init_pipeline()


Expand Down
26 changes: 26 additions & 0 deletions eox_nelp/init_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
patch_user_gender_choices: Change the current openedx gender options (Male, Female, Other)

"""
import os

from django.utils.translation import gettext_noop


Expand All @@ -15,6 +17,7 @@ def run_init_pipeline():
Executes multiple processes that must run before starting the django application.
"""
patch_user_gender_choices()
set_mako_templates()


def patch_user_gender_choices():
Expand All @@ -30,3 +33,26 @@ def patch_user_gender_choices():
('m', gettext_noop('Male')),
('f', gettext_noop('Female')),
)


def set_mako_templates():
"""This method adds the plugin templates to mako
"""
# pylint: disable=import-error, import-outside-toplevel
# This cannot be at the top of the file since this file is imported the plugin initialization
# and therefore the settings has not been set yet
from eox_nelp.course_experience.frontend import templates as course_experience_templates
from eox_nelp.edxapp_wrapper.edxmako import edxmako
from eox_nelp.payment_notifications import templates as payment_notifications_templates
from eox_nelp.stats import templates as stats_templates

module_templates_to_include = [
stats_templates,
course_experience_templates,
payment_notifications_templates,
]

for module in module_templates_to_include:
path_to_templates = os.path.dirname(module.__file__)
if path_to_templates not in edxmako.LOOKUP['main'].directories:
edxmako.paths.add_lookup('main', path_to_templates)
168 changes: 168 additions & 0 deletions eox_nelp/management/commands/send_payment_notification_emails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
""""Management command to send email to payment notifications. If conditions are
satisfied, manage the delivery sending mass email.
To run it use:
`./manage lms send_payment_notification_emails`.
"""

import logging
import time
from datetime import datetime
from django.conf import settings
from django.core.management import BaseCommand
from django.utils import timezone
from django.core.mail import send_mail, send_mass_mail
from django.core import mail
from eox_nelp.notifications.utils import send_email_multialternative
from eox_nelp.edxapp_wrapper.course_overviews import CourseOverview
from django.contrib.auth import get_user_model
from eox_nelp.payment_notifications.models import PaymentNotification

User = get_user_model()

logger = logging.getLogger(__name__)

CASE_1_EMAIL_SUBJECT = "[مهم] مشكلة في الدفع لدورة {course_name} في المعهد العقاري السعودي"

CASE_1_EMAIL_BODY = """
السلام عليكم،

إلى {learner_name} مع التحية،

هذه الرسالة بخصوص مشكلة في عميلة الدفع لتسجيلكم في دورة "{course_name}" في المعهد السعودي العقاري.

حصل خطأ في الدفعة التي جرت بتاريخ {payment_date} وقيمتها {invoice_total_amount} ريال سعودي ولم يتم استيفاء الرسوم المترتبة على الانضمام للدورة.

يرجى الدخول إلى حسابكم في المنصة التعليمية للمعهد وإعادة الدفع: https://srei.futurex.sa/dashboard

أو يمكنكم الدفع مباشرة بالذهاب لهذا الرابط: {payment_url}

إذا كنت متأكداً بأن عملية الدفع الخاصة بك قد تمت بنجاح، الرجاء مراسلتنا على البريد التالي مع إرفاق صورة عن إثبات الدفع مثل كشف الحساب من بطاقة الإتمان أو بطاقة مدى على الإيميل
{support_email}

مع فائق الشكر،

--

فريق المعهد العقاري السعودي على منصة FutureX.sa
"""
CASE_1_EMAIL_HTML_BODY = """
<div style="direction:rtl;">
<p>السلام عليكم،</p>
<p>إلى {learner_name} مع التحية،</p>
<p>هذه الرسالة بخصوص مشكلة في عميلة الدفع لتسجيلكم في دورة "{course_name}" في المعهد السعودي العقاري.</p>
<p>حصل خطأ في الدفعة التي جرت بتاريخ {payment_date} وقيمتها {invoice_total_amount} ريال سعودي ولم يتم استيفاء الرسوم المترتبة على الانضمام للدورة.</p>
<p>يرجى الدخول إلى حسابكم في المنصة التعليمية للمعهد وإعادة الدفع: https://srei.futurex.sa/dashboard</p>
<p>أو يمكنكم الدفع مباشرة بالذهاب لهذا الرابط: {payment_url}</p>
<p>إذا كنت متأكداً بأن عملية الدفع الخاصة بك قد تمت بنجاح، الرجاء مراسلتنا على البريد التالي مع إرفاق صورة عن إثبات الدفع مثل كشف الحساب من بطاقة الإتمان أو بطاقة مدى على الإيميل</p>
<p>{support_email}</p>
<p>مع فائق الشكر،</p>
<p>--</p>
<p>فريق المعهد العقاري السعودي على منصة FutureX.sa</p>
</div>
"""

class Command(BaseCommand):
"""Class command to send case 1 payment notifications."""
def add_arguments(self, parser):
parser.add_argument(
'--payment_notifications_ids',
nargs="+",
type=int,
required=False,
help='Indicate specific payment notification to sent id'
)
parser.add_argument(
'--bcc',
nargs="+",
required=False,
help='Indicate secret bcc to send emails'
)
def handle(self, *args, **kwargs): # lint-amnesty, pylint: disable=too-many-statements
logger.info('----Processing payment notifications to send email-----')
start_time = datetime.now()
payment_notifications_ids = kwargs.get('payment_notifications_ids')
bcc = kwargs.get('bcc')
if payment_notifications_ids:
delivery_qs = PaymentNotification.objects.filter( # pylint: disable=no-member
id__in=payment_notifications_ids,
)
else:
delivery_qs = PaymentNotification.objects.filter( # pylint: disable=no-member
internal_status="case_1",
)
correct_payment_notifications = []
failed_payment_notifications = []
logger.info('----Preparing to send %s emails-----, you have 5 seconds to cancel', len(delivery_qs))
time.sleep(5)
mail_connection = mail.get_connection()
mail_connection.open()
for payment_notification in delivery_qs:
try:
email_multialternative_data = generate_email_multialternative_data(payment_notification)
send_email_multialternative(**email_multialternative_data, connection=mail_connection, bcc=bcc)
correct_payment_notifications.append(payment_notification.id)
except Exception as e:
logger.error("There was an error processing payment notification %s",payment_notification.id)
logger.error(e)
failed_payment_notifications.append(payment_notification.id)

logger.info('----Sending summary emails to managers-----')
mail_connection.close()
send_summary_email(correct_payment_notifications, failed_payment_notifications)
end_time = datetime.now()
script_runtime = end_time - start_time
logger.info('----The command run with a time of approx %s-----', str(script_runtime))


def get_notification_data_from_payment_notification(payment_notification):
"""get data for each payment_notification
"""
course_overview = CourseOverview.objects.get(id=payment_notification.cdtrans_course_id)
user = User.objects.get(id=payment_notification.cdtrans_lms_user_id)
extra_info = getattr(user, "extrainfo", None)
if extra_info:
learner_name = user.extrainfo.arabic_name
elif user.first_name:
learner_name = user.first_name + user.last_name
else:
learner_name = user.username
return {
"course_name": course_overview.display_name,
"learner_name": learner_name,
"payment_date": payment_notification.cdtrans_date.strftime("%d-%m-%Y"), #check format to manage this datetime
"invoice_total_amount": payment_notification.cdtrans_amount,
"payment_url": f"https://srei.ecommerce.futurex.sa/basket/add/?sku={payment_notification.cdtrans_sku}",
"support_email": "[email protected]",
}


def send_summary_email(correct, failed):
"""send refund summarry order email."""
correct_total = len(correct)
failed_total = len(failed)
msg = f"""
Number correct_processed: {correct_total}
Number failed_processed {failed_total}
Correct payment notifications processed: {correct}
Failed payment notifications processed: {failed}
"""
send_mail(
"Emails, correct summary",
msg,
None,
["[email protected]", "[email protected]"],
fail_silently=False,
)


def generate_email_multialternative_data(payment_notification):
"""generate dict data to sent email multialternative"""
notification_data = get_notification_data_from_payment_notification(payment_notification)

return {
"subject": CASE_1_EMAIL_SUBJECT.format(**notification_data),
"plaintext_msg": CASE_1_EMAIL_BODY.format(**notification_data),
"html_msg": CASE_1_EMAIL_HTML_BODY.format(**notification_data),
"recipient_emails": [payment_notification.cdtrans_email]

}
56 changes: 56 additions & 0 deletions eox_nelp/migrations/0005_paymentnotification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 3.2.13 on 2023-07-26 15:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('eox_nelp', '0004_feedback_public_default_to_false'),
]

operations = [
migrations.CreateModel(
name='PaymentNotification',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('cdtrans_lms_user_id', models.IntegerField(blank=True, db_index=True, null=True)),
('cdtrans_sku', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_username', models.CharField(blank=True, max_length=200, null=True)),
('cdtrans_email', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_amount', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_course_id', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_enrollment_id', models.IntegerField(blank=True, null=True)),
('cdtrans_mode', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_cert_status', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_date', models.DateTimeField(blank=True, null=True)),
('cdtrans_response_id', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_status', models.CharField(blank=True, max_length=100, null=True)),
('cdtrans_ecom_order_id', models.IntegerField(blank=True, null=True)),
('cdtrans_ecom_payment_reponse_id', models.IntegerField(blank=True, null=True)),
('cdtrans_extra_data_1', models.CharField(blank=True, max_length=1000, null=True)),
('cdtrans_extra_data_2', models.CharField(blank=True, max_length=1000, null=True)),
('cdtrans_extra_data_3', models.CharField(blank=True, max_length=1000, null=True)),
('cdtrans_extra_data_4', models.TextField(blank=True, null=True)),
('show_msg_case0', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('show_msg_case1', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('show_msg_case2', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('show_trans_info', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('show_msg_custom', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('custom_msg', models.TextField(blank=True, help_text='Not ready', null=True)),
('call_to_action_1_msg', models.CharField(blank=True, help_text='Not ready', max_length=1000, null=True)),
('call_to_action_2_msg', models.CharField(blank=True, help_text='Not ready', max_length=1000, null=True)),
('call_to_action_3_msg', models.CharField(blank=True, help_text='Not ready', max_length=1000, null=True)),
('call_to_action_1_url', models.CharField(blank=True, help_text='Not ready', max_length=1000, null=True)),
('call_to_action_2_url', models.CharField(blank=True, help_text='Not ready', max_length=1000, null=True)),
('call_to_action_3_url', models.CharField(blank=True, help_text='Not ready', max_length=1000, null=True)),
('redirect_from_dashboard', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('redirect_from_course', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('redirect_from_certificate', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('redirect_from_everywhere', models.BooleanField(blank=True, help_text='Not ready', null=True)),
('internal_status', models.CharField(choices=[('case_0', 'case_0'), ('case_1', 'case_1'), ('case_2', 'case_2'), ('pending_manual_eval', 'pending_manual_eval'), ('other_case', 'other_case'), ('ignore', 'ignore'), ('resolution_by_case_0', 'resolution_by_case_0'), ('resolution_by_case_1', 'resolution_by_case_1'), ('resolution_by_case_2', 'resolution_by_case_2'), ('resolution_other', 'resolution_other')], default='case_0', max_length=30)),
('internal_notes', models.CharField(blank=True, max_length=2000, null=True)),
('internal_view_count', models.IntegerField(default=0)),
],
),
]
23 changes: 23 additions & 0 deletions eox_nelp/migrations/0006_auto_20230726_1707.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.2.13 on 2023-07-26 17:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('eox_nelp', '0005_paymentnotification'),
]

operations = [
migrations.AddField(
model_name='paymentnotification',
name='cdtrans_card_last_4_digits',
field=models.CharField(blank=True, max_length=10, null=True),
),
migrations.AlterField(
model_name='paymentnotification',
name='internal_notes',
field=models.TextField(blank=True, max_length=2000, null=True),
),
]
4 changes: 3 additions & 1 deletion eox_nelp/notifications/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ def send_email_multialternative(
html_msg=None,
recipient_emails=None,
from_email=None,
connection=None,
bcc=None,
):
"""
Function to send emails with plaintext msg and html msg.
Returns:
(int): Number of emails sent.
"""
logger.info("------Sending email with subject: %s -------", subject)
msg = EmailMultiAlternatives(subject, plaintext_msg, from_email, recipient_emails)
msg = EmailMultiAlternatives(subject, plaintext_msg, from_email, recipient_emails, connection=connection, bcc=bcc)
msg.attach_alternative(html_msg, "text/html")
return msg.send()

Expand Down
Empty file.
Loading
Loading