240 lines
9.3 KiB
Python
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 |