custom_ui/custom_ui/services/email_service.py
2026-02-06 13:10:34 -06:00

240 lines
9.3 KiB
Python

import frappe
from frappe.utils import get_url
class EmailService:
@staticmethod
def get_customer_email(customer_name: str, doctype: str = "Customer") -> str | None:
"""
Get the primary email for a customer or lead.
Args:
customer_name: Name of the Customer or Lead
doctype: Either "Customer" or "Lead"
Returns:
Email address if found, None otherwise
"""
try:
customer_doc = frappe.get_doc(doctype, customer_name)
email = None
# Try primary_contact field
if hasattr(customer_doc, 'primary_contact') and customer_doc.primary_contact:
try:
primary_contact = frappe.get_doc("Contact", customer_doc.primary_contact)
email = primary_contact.email_id
except Exception as e:
print(f"Warning: Could not get primary_contact: {str(e)}")
# Fallback to customer_primary_contact
if not email and hasattr(customer_doc, 'customer_primary_contact') and customer_doc.customer_primary_contact:
try:
primary_contact = frappe.get_doc("Contact", customer_doc.customer_primary_contact)
email = primary_contact.email_id
except Exception as e:
print(f"Warning: Could not get customer_primary_contact: {str(e)}")
# Last resort - get any contact linked to this customer/lead
if not email:
contact_links = frappe.get_all("Dynamic Link",
filters={
"link_doctype": doctype,
"link_name": customer_name,
"parenttype": "Contact"
},
pluck="parent"
)
if contact_links:
try:
contact = frappe.get_doc("Contact", contact_links[0])
email = contact.email_id
except Exception as e:
print(f"Warning: Could not get contact from dynamic link: {str(e)}")
return email
except Exception as e:
print(f"ERROR: Failed to get email for {doctype} {customer_name}: {str(e)}")
return None
@staticmethod
def send_templated_email(
recipients: str | list,
subject: str,
template_path: str,
template_context: dict,
doctype: str = None,
docname: str = None,
cc: str | list = None,
bcc: str | list = None,
attachments: list = None
) -> bool:
"""
Send an email using a Jinja2 template.
Args:
recipients: Email address(es) to send to
subject: Email subject line
template_path: Path to the Jinja2 template (relative to app root)
template_context: Dictionary of variables to pass to template
doctype: Optional doctype to link email to
docname: Optional document name to link email to
cc: Optional CC recipients
bcc: Optional BCC recipients
attachments: Optional list of attachments
Returns:
True if email sent successfully, False otherwise
"""
try:
# Render the email template
message = frappe.render_template(template_path, template_context)
# Prepare sendmail arguments
email_args = {
"recipients": recipients,
"subject": subject,
"message": message,
}
if doctype:
email_args["doctype"] = doctype
if docname:
email_args["name"] = docname
if cc:
email_args["cc"] = cc
if bcc:
email_args["bcc"] = bcc
if attachments:
email_args["attachments"] = attachments
# Send email
frappe.sendmail(**email_args)
print(f"DEBUG: Email sent successfully to {recipients}")
return True
except Exception as e:
print(f"ERROR: Failed to send email: {str(e)}")
frappe.log_error(f"Failed to send email to {recipients}: {str(e)}", "Email Service Error")
return False
@staticmethod
def send_downpayment_email(sales_order_name: str) -> bool:
"""
Send a down payment email for a Sales Order.
Args:
sales_order_name: Name of the Sales Order
Returns:
True if email sent successfully, False otherwise
"""
try:
doc = frappe.get_doc("Sales Order", sales_order_name)
# Get customer email
email = EmailService.get_customer_email(doc.customer, "Customer")
if not email:
print(f"ERROR: No email found for customer {doc.customer}, cannot send down payment email")
return False
# Prepare template context
half_down_amount = doc.custom_halfdown_amount or (doc.grand_total / 2)
base_url = get_url()
template_context = {
"company_name": doc.company,
"customer_name": doc.customer_name or doc.customer,
"sales_order_number": doc.name,
"total_amount": frappe.utils.fmt_money(half_down_amount, currency=doc.currency),
"base_url": base_url
}
# Send email
template_path = "custom_ui/templates/emails/downpayment.html"
subject = f"Down Payment Required - {doc.company} - {doc.name}"
return EmailService.send_templated_email(
recipients=email,
subject=subject,
template_path=template_path,
template_context=template_context,
doctype="Sales Order",
docname=doc.name
)
except Exception as e:
print(f"ERROR: Failed to send down payment email for {sales_order_name}: {str(e)}")
frappe.log_error(f"Failed to send down payment email for {sales_order_name}: {str(e)}", "Down Payment Email Error")
return False
@staticmethod
def send_invoice_email(sales_invoice_name: str) -> bool:
"""
Send an invoice email for a Sales Invoice.
Args:
sales_invoice_name: Name of the Sales Invoice
Returns:
True if email sent successfully, False otherwise
"""
try:
doc = frappe.get_doc("Sales Invoice", sales_invoice_name)
# Get customer email
email = EmailService.get_customer_email(doc.customer, "Customer")
if not email:
print(f"ERROR: No email found for customer {doc.customer}, cannot send invoice email")
return False
# Calculate amounts
outstanding_amount = doc.outstanding_amount
paid_amount = doc.grand_total - outstanding_amount
# Get related Sales Order if available
sales_order = None
if hasattr(doc, 'items') and doc.items:
for item in doc.items:
if item.sales_order:
sales_order = item.sales_order
break
# Prepare template context
base_url = get_url()
template_context = {
"company_name": doc.company,
"customer_name": doc.customer_name or doc.customer,
"invoice_number": doc.name,
"invoice_date": doc.posting_date,
"due_date": doc.due_date,
"grand_total": frappe.utils.fmt_money(doc.grand_total, currency=doc.currency),
"outstanding_amount": frappe.utils.fmt_money(outstanding_amount, currency=doc.currency),
"paid_amount": frappe.utils.fmt_money(paid_amount, currency=doc.currency),
"sales_order": sales_order,
"base_url": base_url,
"payment_url": f"{base_url}/api/method/custom_ui.api.public.payments.invoice_stripe_payment?sales_invoice={doc.name}" if outstanding_amount > 0 else None
}
# Send email
template_path = "custom_ui/templates/emails/invoice.html"
subject = f"Invoice {doc.name} - {doc.company}"
return EmailService.send_templated_email(
recipients=email,
subject=subject,
template_path=template_path,
template_context=template_context,
doctype="Sales Invoice",
docname=doc.name
)
except Exception as e:
print(f"ERROR: Failed to send invoice email for {sales_invoice_name}: {str(e)}")
frappe.log_error(f"Failed to send invoice email for {sales_invoice_name}: {str(e)}", "Invoice Email Error")
return False