From 089ebf12a09234673a2dd97f18f5b3e66344c9b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Gurn=C3=ADk?= Date: Sat, 2 Nov 2024 14:08:34 +0100 Subject: [PATCH 1/8] Added new modal, ability to use email variables, expanded send_email function --- backend/core/api/base/modal.py | 3 +- backend/core/api/emails/send.py | 160 +++++++++++++++++- backend/core/api/emails/urls.py | 5 + backend/core/data/default_feature_flags.py | 2 +- backend/core/types/emails.py | 2 + .../modals/send_email_from_invoice.html | 100 +++++++++++ .../single/manage_access/_table_row.html | 4 +- settings/helpers.py | 8 +- 8 files changed, 277 insertions(+), 7 deletions(-) create mode 100644 frontend/templates/modals/send_email_from_invoice.html diff --git a/backend/core/api/base/modal.py b/backend/core/api/base/modal.py index cc252fe3..e8c497d7 100644 --- a/backend/core/api/base/modal.py +++ b/backend/core/api/base/modal.py @@ -137,7 +137,7 @@ def open_modal(request: WebRequest, modal_name, context_type=None, context_value else: context[context_type] = context_value - if modal_name == "send_single_email" or modal_name == "send_bulk_email": + if modal_name == "send_single_email" or modal_name == "send_bulk_email" or modal_name == "send_email_from_invoice": if not get_feature_status("areUserEmailsAllowed"): messages.error(request, "Emails are disabled") return render(request, "base/toast.html") @@ -145,6 +145,7 @@ def open_modal(request: WebRequest, modal_name, context_type=None, context_value quota = QuotaLimit.objects.prefetch_related("quota_overrides").get(slug="emails-email_character_count") context["content_max_length"] = quota.get_quota_limit(user=request.user, quota_limit=quota) context["email_list"] = Client.filter_by_owner(owner=request.actor).filter(email__isnull=False).values_list("email", flat=True) + context["invoice_url"] = context_value if context_type == "invoice_code_send": invoice_url: InvoiceURL | None = InvoiceURL.objects.filter(uuid=context_value).prefetch_related("invoice").first() diff --git a/backend/core/api/emails/send.py b/backend/core/api/emails/send.py index 317a59ad..fa6517c1 100644 --- a/backend/core/api/emails/send.py +++ b/backend/core/api/emails/send.py @@ -18,6 +18,7 @@ from backend.core.data.default_email_templates import email_footer from backend.decorators import feature_flag_check, web_require_scopes from backend.decorators import htmx_only +from backend.finance.models import Invoice, InvoiceURL from backend.models import Client from backend.models import EmailSendStatus from backend.models import QuotaLimit @@ -65,6 +66,163 @@ def send_bulk_email_view(request: WebRequest) -> HttpResponse: return _send_bulk_email_view(request) +@require_POST +@htmx_only("emails:dashboard") +@feature_flag_check("areUserEmailsAllowed", status=True, api=True, htmx=True) +@web_require_scopes("emails:send", False, False, "emails:dashboard") +def send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: + # email_count = len(request.POST.getlist("emails")) - 1 + + # check_usage = quota_usage_check_under(request, "emails-single-count", add=email_count, api=True, htmx=True) + # if not isinstance(check_usage, bool): + # return check_usage + return _send_invoice_email_view(request, uuid) + + +def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: + emails: list[str] = request.POST.getlist("emails") + subject: str = request.POST.get("subject", "") + message: str = request.POST.get("content", "") + cc_emails = request.POST.get("cc_emails", "").split(",") if request.POST.get("cc_emails") else [] + bcc_emails = request.POST.get("bcc_emails", "").split(",") if request.POST.get("bcc_emails") else [] + invoiceurl_uuid = uuid + invoice_id = InvoiceURL.objects.filter(uuid=invoiceurl_uuid).values_list("invoice_id", flat=True).first() + invoice = Invoice.objects.filter(id=invoice_id).first() + + if request.user.logged_in_as_team: + clients = Client.objects.filter(organization=request.user.logged_in_as_team, email__in=emails) + else: + clients = Client.objects.filter(user=request.user, email__in=emails) + + validated_bulk = validate_bulk_inputs(request=request, emails=emails, clients=clients, message=message, subject=subject) + + if validated_bulk: + messages.error(request, validated_bulk) + return render(request, "base/toast.html") + + message += email_footer() + message_single_line_html = message.replace("\r\n", "
").replace("\n", "
") + + email_list: list[BulkEmailEmailItem] = [] + + for email in emails: + client = clients.filter(email=email).first() + + email_data = { + "first_name": invoice.client_name if invoice.client_name else "User", + "invoice_id": invoice.id, + "invoice_ref": invoice.reference or invoice.invoice_number or invoice.id, + "due_date": invoice.date_due.strftime("%A, %B %d, %Y"), + "amount_due": invoice.get_total_price(), + "currency": invoice.currency, + "currency_symbol": invoice.get_currency_symbol(), + "product_list": [], # todo + "company_name": invoice.self_company or invoice.self_name or "MyFinances Customer", + "invoice_link": get_var("SITE_URL") + "/invoice/" + invoiceurl_uuid, + } + + email_list.append( + BulkEmailEmailItem( + destination=email, + cc=cc_emails, + bcc=bcc_emails, + template_data={ + "users_name": client.name.split()[0] if client else "User", + "content_text": Template(message).safe_substitute(email_data), + "content_html": Template(message_single_line_html).safe_substitute(email_data), + }, + ) + ) + + if get_var("DEBUG", "").lower() == "true": + print( + { + "email_list": email_list, + "template_name": "user_send_client_email", + "default_template_data": { + "sender_name": request.user.first_name or request.user.email, + "sender_id": request.user.id, + "subject": subject, + }, + }, + ) + messages.success(request, f"Successfully emailed {len(email_list)} people.") + return render(request, "base/toast.html") + + EMAIL_SENT = send_email( + destination=emails, + subject=subject, + content={ + "template_name": "user_send_client_email", + "template_data": { + "subject": subject, + "sender_name": request.user.first_name or request.user.email, + "sender_id": request.user.id, + "content_text": Template(message).safe_substitute(email_data), + "content_html": Template(message_single_line_html).safe_substitute(email_data), + }, + }, + from_address=request.user.email, + cc=cc_emails, + bcc=bcc_emails + ) + + if EMAIL_SENT.failed: + messages.error(request, EMAIL_SENT.error) + return render(request, "base/toast.html") + + + # todo - fix + + EMAIL_RESPONSES: Iterator[tuple[BulkEmailEmailItem, BulkEmailEntryResultTypeDef]] = zip( + email_list, EMAIL_SENT.response.get("BulkEmailEntryResults") # type: ignore[arg-type] + ) + + if request.user.logged_in_as_team: + SEND_STATUS_OBJECTS: list[EmailSendStatus] = EmailSendStatus.objects.bulk_create( + [ + EmailSendStatus( + organization=request.user.logged_in_as_team, + sent_by=request.user, + recipient=response[0].destination, + aws_message_id=response[1].get("MessageId"), + status="pending", + ) + for response in EMAIL_RESPONSES + ] + ) + else: + SEND_STATUS_OBJECTS = EmailSendStatus.objects.bulk_create( + [ + EmailSendStatus( + user=request.user, + sent_by=request.user, + recipient=response[0].destination, + aws_message_id=response[1].get("MessageId"), + status="pending", + ) + for response in EMAIL_RESPONSES + ] + ) + + messages.success(request, f"Successfully emailed {len(email_list)} people.") + + try: + quota_limits = QuotaLimit.objects.filter(slug__in=["emails-single-count", "emails-bulk-count"]) + + QuotaUsage.objects.bulk_create( + [ + QuotaUsage(user=request.user, quota_limit=quota_limits.get(slug="emails-single-count"), extra_data=status.id) + for status in SEND_STATUS_OBJECTS + ] + + [QuotaUsage(user=request.user, quota_limit=quota_limits.get(slug="emails-bulk-count"))] + ) + except QuotaLimit.DoesNotExist: + ... + + return render(request, "base/toast.html") + + def _send_bulk_email_view(request: WebRequest) -> HttpResponse: emails: list[str] = request.POST.getlist("emails") subject: str = request.POST.get("subject", "") @@ -120,7 +278,7 @@ def _send_bulk_email_view(request: WebRequest) -> HttpResponse: "sender_id": request.user.id, "subject": subject, }, - } + }, ) messages.success(request, f"Successfully emailed {len(email_list)} people.") return render(request, "base/toast.html") diff --git a/backend/core/api/emails/urls.py b/backend/core/api/emails/urls.py index 0f5217e7..dd6a80d4 100644 --- a/backend/core/api/emails/urls.py +++ b/backend/core/api/emails/urls.py @@ -15,6 +15,11 @@ send.send_bulk_email_view, name="send bulk", ), + path( + "send/invoice/", + send.send_invoice_email_view, + name="send invoice", + ), path("fetch/", fetch.fetch_all_emails, name="fetch"), path("get_status//", status.get_status_view, name="get_status"), path("refresh_statuses/", status.refresh_all_statuses_view, name="refresh statuses"), diff --git a/backend/core/data/default_feature_flags.py b/backend/core/data/default_feature_flags.py index fa590536..e4b7f84e 100644 --- a/backend/core/data/default_feature_flags.py +++ b/backend/core/data/default_feature_flags.py @@ -17,7 +17,7 @@ class FeatureFlag: description="Invoice Scheduling allows for clients to create invoice schedules that send and invoice at a specific date.", default=False, ), - FeatureFlag(name="areUserEmailsAllowed", description="Are users allowed to send emails from YOUR DOMAIN to customers", default=False), + FeatureFlag(name="areUserEmailsAllowed", description="Are users allowed to send emails from YOUR DOMAIN to customers", default=True), FeatureFlag( name="areInvoiceRemindersEnabled", description="Invoice Reminders allow for clients to be reminded to pay an invoice.", diff --git a/backend/core/types/emails.py b/backend/core/types/emails.py index cb1abc08..15d289c7 100644 --- a/backend/core/types/emails.py +++ b/backend/core/types/emails.py @@ -25,6 +25,8 @@ class SingleEmailInput: ConfigurationSetName: str | None = None from_address: str | None = None from_address_name_prefix: str | None = None + cc: list[str] = field(default_factory=list) + bcc: list[str] = field(default_factory=list) @dataclass diff --git a/frontend/templates/modals/send_email_from_invoice.html b/frontend/templates/modals/send_email_from_invoice.html new file mode 100644 index 00000000..0a478531 --- /dev/null +++ b/frontend/templates/modals/send_email_from_invoice.html @@ -0,0 +1,100 @@ +{% component_block "modal" id="modal_send_invoice_email" start_open="true" title="Send Invoice Email" %} +{% fill "content" %} + +{% endfill %} +{% endcomponent_block %} diff --git a/frontend/templates/pages/invoices/single/manage_access/_table_row.html b/frontend/templates/pages/invoices/single/manage_access/_table_row.html index 7a0c31db..d508b4c8 100644 --- a/frontend/templates/pages/invoices/single/manage_access/_table_row.html +++ b/frontend/templates/pages/invoices/single/manage_access/_table_row.html @@ -15,11 +15,11 @@ diff --git a/settings/helpers.py b/settings/helpers.py index 2b225ad8..df65a665 100644 --- a/settings/helpers.py +++ b/settings/helpers.py @@ -26,7 +26,7 @@ # NEEDS REFACTOR -env = environ.Env(DEBUG=(bool, False)) +#env = environ.Env(DEBUG=(bool, False)) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) environ.Env.read_env(os.path.join(BASE_DIR, ".env")) env = environ.Env() @@ -55,7 +55,7 @@ def increment_rate_limit(request, group): EMAIL_CLIENT: SESV2Client = boto3.client( "sesv2", - region_name="eu-west-2", + region_name="eu-west-2" # aws_access_key_id=get_var("AWS_SES_ACCESS_KEY_ID"), # aws_secret_access_key=get_var("AWS_SES_SECRET_ACCESS_KEY"), ) @@ -91,6 +91,8 @@ def send_email( ConfigurationSetName: str | None = None, from_address: str | None = None, from_address_name_prefix: str | None = None, + cc: list[str] | None = None, + bcc: list[str] | None = None ) -> SingleEmailSendServiceResponse: """ Args: @@ -107,6 +109,8 @@ def send_email( ConfigurationSetName=ConfigurationSetName, from_address=from_address, from_address_name_prefix=from_address_name_prefix, + cc=cc, + bcc=bcc ) if get_var("DEBUG", "").lower() == "true": From 808b5c911354b4e506207cd874b5ef67ba5147ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Gurn=C3=ADk?= Date: Wed, 6 Nov 2024 16:12:27 +0100 Subject: [PATCH 2/8] Added new email templates, email sending when invoice changes status --- backend/core/api/emails/send.py | 9 ++-- backend/core/data/default_email_templates.py | 56 ++++++++++++++++++++ backend/core/data/default_feature_flags.py | 2 +- backend/core/signals/signals.py | 31 +++++++++++ backend/finance/api/invoices/edit.py | 32 +++++++++++ settings/helpers.py | 10 ++-- 6 files changed, 131 insertions(+), 9 deletions(-) diff --git a/backend/core/api/emails/send.py b/backend/core/api/emails/send.py index fa6517c1..d40e95ff 100644 --- a/backend/core/api/emails/send.py +++ b/backend/core/api/emails/send.py @@ -87,7 +87,9 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: bcc_emails = request.POST.get("bcc_emails", "").split(",") if request.POST.get("bcc_emails") else [] invoiceurl_uuid = uuid invoice_id = InvoiceURL.objects.filter(uuid=invoiceurl_uuid).values_list("invoice_id", flat=True).first() - invoice = Invoice.objects.filter(id=invoice_id).first() + + if invoice_id is not None: + invoice = Invoice.objects.filter(id=invoice_id).first() if request.user.logged_in_as_team: clients = Client.objects.filter(organization=request.user.logged_in_as_team, email__in=emails) @@ -121,6 +123,8 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: "invoice_link": get_var("SITE_URL") + "/invoice/" + invoiceurl_uuid, } + print(email_data) + email_list.append( BulkEmailEmailItem( destination=email, @@ -164,14 +168,13 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: }, from_address=request.user.email, cc=cc_emails, - bcc=bcc_emails + bcc=bcc_emails, ) if EMAIL_SENT.failed: messages.error(request, EMAIL_SENT.error) return render(request, "base/toast.html") - # todo - fix EMAIL_RESPONSES: Iterator[tuple[BulkEmailEmailItem, BulkEmailEntryResultTypeDef]] = zip( diff --git a/backend/core/data/default_email_templates.py b/backend/core/data/default_email_templates.py index 511ab105..88153ebe 100644 --- a/backend/core/data/default_email_templates.py +++ b/backend/core/data/default_email_templates.py @@ -69,3 +69,59 @@ def email_footer() -> str: """ ).strip() ) + + +def invoice_state_pending_template() -> str: + return dedent( + """ + Hi $first_name, + + Your invoice #$invoice_id is now available and is due by $due_date. Please make your payment at your earliest convenience. + + Balance Due: $currency_symbol$amount_due $currency + View or Pay Online: $invoice_link + If you are paying by standing order, no further action is required. Should you have any questions or concerns, feel free to reach out to us. + + Thank you for your prompt attention to this matter. + + Best regards, + $company_name + """ + ).strip() + + +def invoice_state_paid_template() -> str: + return dedent( + """ + Hi $first_name, + + The invoice #$invoice_id has just been paid. + + If you have any questions or concerns, please feel free to contact us. + + Many thanks, + $company_name + """ + ).strip() + + +def invoice_state_overdue_template() -> str: + return dedent( + """ + Hi $first_name, + + We wanted to remind you that invoice #$invoice_id is now overdue. Please arrange payment as soon as possible to ensure there’s no interruption in your service. If you’ve already made the payment, kindly disregard this message—our apologies for any confusion. + + Here are the details for your convenience: + + Balance Due: $currency_symbol$amount_due $currency + Due Date: $due_date + + If you have any questions or concerns, we’re happy to help. Please don’t hesitate to reach out. + + Thank you for your prompt attention to this matter. + + Warm regards, + $company_name + """ + ).strip() diff --git a/backend/core/data/default_feature_flags.py b/backend/core/data/default_feature_flags.py index e4b7f84e..fa590536 100644 --- a/backend/core/data/default_feature_flags.py +++ b/backend/core/data/default_feature_flags.py @@ -17,7 +17,7 @@ class FeatureFlag: description="Invoice Scheduling allows for clients to create invoice schedules that send and invoice at a specific date.", default=False, ), - FeatureFlag(name="areUserEmailsAllowed", description="Are users allowed to send emails from YOUR DOMAIN to customers", default=True), + FeatureFlag(name="areUserEmailsAllowed", description="Are users allowed to send emails from YOUR DOMAIN to customers", default=False), FeatureFlag( name="areInvoiceRemindersEnabled", description="Invoice Reminders allow for clients to be reminded to pay an invoice.", diff --git a/backend/core/signals/signals.py b/backend/core/signals/signals.py index 751a51d3..81ad329c 100644 --- a/backend/core/signals/signals.py +++ b/backend/core/signals/signals.py @@ -1,8 +1,13 @@ from __future__ import annotations +from string import Template + from django.core.cache import cache from django.core.cache.backends.redis import RedisCacheClient +from backend.core.data.default_email_templates import email_footer, invoice_state_overdue_template +from backend.finance.models import Invoice + cache: RedisCacheClient = cache from django.core.files.storage import default_storage from django.db.models.signals import pre_save, post_delete, post_save, pre_delete @@ -96,3 +101,29 @@ def send_welcome_email(sender, instance: User, created, **kwargs): email = send_email(destination=instance.email, subject="Welcome to MyFinances", content=email_message) # User.send_welcome_email(instance) + + +@receiver(post_save, sender=Invoice) +def send_overdue_invoice_email(sender, instance: Invoice, **kwargs): + if instance.dynamic_status == "overdue": + client_email = instance.client_to.email if instance.client_to else instance.client_email + if client_email: + message: str = invoice_state_overdue_template() + email_footer() + + user_data = { + "first_name": instance.client_to.name.split(" ")[0] if instance.client_to else instance.client_name, + "invoice_id": instance.id, + "due_date": instance.date_due.strftime("%A, %B %d, %Y"), + "amount_due": instance.get_total_price(), + "currency": instance.currency, + "currency_symbol": instance.get_currency_symbol(), + "company_name": instance.self_company or instance.self_name or "MyFinances Customer", + } + + output: str = Template(message).safe_substitute(user_data) + + send_email( + destination=client_email, + subject=f"Invoice #{instance.id} from {instance.self_company or instance.self_name} is overdue", + content=output, + ) diff --git a/backend/finance/api/invoices/edit.py b/backend/finance/api/invoices/edit.py index a6561a85..a9d4d32f 100644 --- a/backend/finance/api/invoices/edit.py +++ b/backend/finance/api/invoices/edit.py @@ -1,13 +1,16 @@ from datetime import datetime +from string import Template from django.contrib import messages from django.http import HttpRequest, JsonResponse, HttpResponse from django.shortcuts import render, redirect from django.views.decorators.http import require_http_methods, require_POST +from backend.core.data.default_email_templates import invoice_state_paid_template, invoice_state_pending_template, email_footer from backend.decorators import web_require_scopes from backend.finance.models import Invoice from backend.core.types.htmx import HtmxHttpRequest +from settings.helpers import send_email @require_http_methods(["POST"]) @@ -98,6 +101,35 @@ def change_status(request: HtmxHttpRequest, invoice_id: int, status: str) -> Htt invoice.status = status invoice.save() + if status in ["pending", "paid"] and invoice.dynamic_status != "overdue": + client_email = invoice.client_to.email if invoice.client_to else invoice.client_email + if client_email: + message_template = invoice_state_pending_template() + subject = f"Invoice #{invoice.id} is now available" + + if status == "paid": + message_template = invoice_state_paid_template() + subject = f"Invoice #{invoice.id} has been paid" + + message_template += email_footer() + + email_data = { + "first_name": invoice.client_to.name.split(" ")[0] if invoice.client_to else invoice.client_name, + "invoice_id": invoice.id, + "status": status.capitalize(), + "amount_due": invoice.get_total_price(), + "currency_symbol": invoice.get_currency_symbol(), + "due_date": invoice.date_due.strftime("%A, %B %d, %Y"), + "company_name": invoice.self_company or invoice.self_name or "MyFinances Customer", + } + message = Template(message_template).safe_substitute(email_data) + + send_email( + destination=client_email, + subject=subject, + content=message, + ) + send_message(request, f"Invoice status been changed to {status}", success=True) return render(request, "pages/invoices/dashboard/_modify_payment_status.html", {"status": status, "invoice_id": invoice_id}) diff --git a/settings/helpers.py b/settings/helpers.py index df65a665..893305cb 100644 --- a/settings/helpers.py +++ b/settings/helpers.py @@ -26,7 +26,7 @@ # NEEDS REFACTOR -#env = environ.Env(DEBUG=(bool, False)) +# env = environ.Env(DEBUG=(bool, False)) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) environ.Env.read_env(os.path.join(BASE_DIR, ".env")) env = environ.Env() @@ -55,7 +55,7 @@ def increment_rate_limit(request, group): EMAIL_CLIENT: SESV2Client = boto3.client( "sesv2", - region_name="eu-west-2" + region_name="eu-west-2", # aws_access_key_id=get_var("AWS_SES_ACCESS_KEY_ID"), # aws_secret_access_key=get_var("AWS_SES_SECRET_ACCESS_KEY"), ) @@ -91,8 +91,8 @@ def send_email( ConfigurationSetName: str | None = None, from_address: str | None = None, from_address_name_prefix: str | None = None, - cc: list[str] | None = None, - bcc: list[str] | None = None + cc: list[str] = [], + bcc: list[str] = [], ) -> SingleEmailSendServiceResponse: """ Args: @@ -110,7 +110,7 @@ def send_email( from_address=from_address, from_address_name_prefix=from_address_name_prefix, cc=cc, - bcc=bcc + bcc=bcc, ) if get_var("DEBUG", "").lower() == "true": From 9d0ef98a7410bf97dde9c6b5e15c8d0ccf45b259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Gurn=C3=ADk?= Date: Sun, 10 Nov 2024 21:40:50 +0100 Subject: [PATCH 3/8] fixed product_list variable --- backend/core/api/emails/send.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/core/api/emails/send.py b/backend/core/api/emails/send.py index d40e95ff..f0896ad5 100644 --- a/backend/core/api/emails/send.py +++ b/backend/core/api/emails/send.py @@ -88,8 +88,14 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: invoiceurl_uuid = uuid invoice_id = InvoiceURL.objects.filter(uuid=invoiceurl_uuid).values_list("invoice_id", flat=True).first() - if invoice_id is not None: - invoice = Invoice.objects.filter(id=invoice_id).first() + if invoice_id is None: + messages.error(request, "Invalid Invoice") + return render(request, "base/toast.html") + + invoice = Invoice.objects.filter(id=invoice_id).first() + + item_names = invoice.items.values_list("name", flat=True) + item_names_list = list(item_names) if request.user.logged_in_as_team: clients = Client.objects.filter(organization=request.user.logged_in_as_team, email__in=emails) @@ -118,13 +124,11 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: "amount_due": invoice.get_total_price(), "currency": invoice.currency, "currency_symbol": invoice.get_currency_symbol(), - "product_list": [], # todo + "product_list": "\n".join(f"- {item_name}" for item_name in item_names_list), "company_name": invoice.self_company or invoice.self_name or "MyFinances Customer", "invoice_link": get_var("SITE_URL") + "/invoice/" + invoiceurl_uuid, } - print(email_data) - email_list.append( BulkEmailEmailItem( destination=email, From 3f7c3ac2f1165482e786cdc4c829b467b0e09a7a Mon Sep 17 00:00:00 2001 From: TreyWW <73353716+TreyWW@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:17:13 +0000 Subject: [PATCH 4/8] few minor changes --- backend/core/api/base/modal.py | 3 +-- backend/core/data/default_email_templates.py | 3 +-- backend/finance/api/invoices/edit.py | 2 +- .../modals/send_email_from_invoice.html | 16 +++++++--------- infrastructure/aws/pulumi/emails.py | 8 +++----- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/backend/core/api/base/modal.py b/backend/core/api/base/modal.py index b20e6fe7..4814d84b 100644 --- a/backend/core/api/base/modal.py +++ b/backend/core/api/base/modal.py @@ -158,8 +158,7 @@ def open_modal(request: WebRequest, modal_name, context_type=None, context_value if value is not None ] - context["email_list"] = list(context["email_list"]) + context["selected_clients"] - + context["email_list"] = list(filter(lambda i: i is not "", list(context["email_list"]) + context["selected_clients"])) elif modal_name == "invoices_to_destination": if existing_client := request.GET.get("client"): context["existing_client_id"] = existing_client diff --git a/backend/core/data/default_email_templates.py b/backend/core/data/default_email_templates.py index 88153ebe..944c3856 100644 --- a/backend/core/data/default_email_templates.py +++ b/backend/core/data/default_email_templates.py @@ -64,8 +64,7 @@ def email_footer() -> str: """ Note: This is an automated email sent by MyFinances on behalf of '$company_name'. - If you believe this email is spam or fraudulent, please do not pay the invoice and report it to us immediately at report@myfinances.cloud. - Once reported, we will open a case for investigation. In some cases, eligible reports may qualify for a reward, determined on a case-by-case basis. + If you suspect this email is spam or fraudulent, please let us know immediately to report@myfinances.cloud and avoid making payments. """ ).strip() ) diff --git a/backend/finance/api/invoices/edit.py b/backend/finance/api/invoices/edit.py index fd3d153f..5a4be297 100644 --- a/backend/finance/api/invoices/edit.py +++ b/backend/finance/api/invoices/edit.py @@ -1,5 +1,5 @@ from datetime import datetime -from string import Template, Literal +from string import Template from django.contrib import messages from django.http import HttpRequest, JsonResponse, HttpResponse from django.shortcuts import render, redirect diff --git a/frontend/templates/modals/send_email_from_invoice.html b/frontend/templates/modals/send_email_from_invoice.html index 0a478531..b5bcae2b 100644 --- a/frontend/templates/modals/send_email_from_invoice.html +++ b/frontend/templates/modals/send_email_from_invoice.html @@ -19,7 +19,7 @@ {% if email_list %}{% endif %} {% for email in email_list %} + {% if email in selected_clients %}selected{% endif %}>{{ email }} {% empty %} {% endfor %} @@ -44,7 +44,11 @@
- +
@@ -53,12 +57,6 @@
- -
-
- +
From 10a9f05c3f641bce95d3074cdd0f9702241ec4c4 Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:03:53 +0000 Subject: [PATCH 7/8] mypy: silenced strange "index" error for function type Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- backend/core/api/emails/send.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/api/emails/send.py b/backend/core/api/emails/send.py index 76f2ceb7..532f9dc6 100644 --- a/backend/core/api/emails/send.py +++ b/backend/core/api/emails/send.py @@ -185,7 +185,7 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: send_email( destination=email_item.destination, subject=subject, - content=email_item.template_data["content_text"], + content=email_item.template_data["content_text"], # type: ignore[index] from_address=request.user.email, cc=email_item.cc, bcc=email_item.bcc, From fe37e9f6e30fe527df6cbea1502abab1d240d314 Mon Sep 17 00:00:00 2001 From: Trey <73353716+TreyWW@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:13:29 +0000 Subject: [PATCH 8/8] fix: use new invoice reference rather than old invoice_number Signed-off-by: Trey <73353716+TreyWW@users.noreply.github.com> --- backend/core/api/emails/send.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/core/api/emails/send.py b/backend/core/api/emails/send.py index 532f9dc6..1052cb0e 100644 --- a/backend/core/api/emails/send.py +++ b/backend/core/api/emails/send.py @@ -119,7 +119,7 @@ def _send_invoice_email_view(request: WebRequest, uuid) -> HttpResponse: email_data = { "first_name": invoice.client_name if invoice.client_name else "User", "invoice_id": invoice.id, - "invoice_ref": invoice.reference or invoice.invoice_number or invoice.id, + "invoice_ref": invoice.reference or invoice.id, "due_date": invoice.date_due.strftime("%A, %B %d, %Y"), "amount_due": invoice.get_total_price(), "currency": invoice.currency,