From 36963a8e0408af1171ad0ee170ac0224ebeebc5b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 9 Jul 2019 15:14:13 +0530 Subject: [PATCH] feat: Email Campaign --- .../email_campaign/email_campaign.json | 66 +++++---- .../doctype/email_campaign/email_campaign.py | 128 ++++++++++++++---- .../email_campaign/email_campaign_list.js | 11 ++ erpnext/hooks.py | 3 +- 4 files changed, 155 insertions(+), 53 deletions(-) create mode 100644 erpnext/crm/doctype/email_campaign/email_campaign_list.js diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json index d7113f6b4e7..66b35467cfd 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.json +++ b/erpnext/crm/doctype/email_campaign/email_campaign.json @@ -7,20 +7,23 @@ "field_order": [ "campaign_section", "campaign_name", - "lead", - "column_break_4", + "email_campaign_for", "start_date", + "column_break_4", + "sender", + "recipient", + "end_date", "status", "email_schedule_section", "email_schedule", - "naming_series", - "amended_from" + "unsubscribed", + "naming_series" ], "fields": [ { "fieldname": "campaign_section", "fieldtype": "Section Break", - "label": "CAMPAIGN " + "label": "Campaign" }, { "fieldname": "campaign_name", @@ -31,19 +34,10 @@ "reqd": 1 }, { - "fieldname": "lead", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Lead", - "options": "Lead", - "reqd": 1 - }, - { - "default": "Started", "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "\nDraft\nSubmitted\nStarted\nIn Progress\nCompleted" + "options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed" }, { "fieldname": "column_break_4", @@ -58,7 +52,7 @@ { "fieldname": "email_schedule_section", "fieldtype": "Section Break", - "label": "EMAIL SCHEDULE" + "label": "Email Schedule" }, { "fieldname": "email_schedule", @@ -75,17 +69,41 @@ "reqd": 1 }, { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Email Campaign", - "print_hide": 1, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", "read_only": 1 + }, + { + "default": "Lead", + "fieldname": "email_campaign_for", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Email Campaign For ", + "options": "\nLead\nContact" + }, + { + "fieldname": "recipient", + "fieldtype": "Dynamic Link", + "label": "Recipient", + "options": "email_campaign_for", + "reqd": 1 + }, + { + "default": "__user", + "fieldname": "sender", + "fieldtype": "Link", + "label": "Sender", + "options": "User" + }, + { + "default": "0", + "fieldname": "unsubscribed", + "fieldtype": "Check", + "label": "Unsubscribed" } ], - "is_submittable": 1, - "modified": "2019-06-30 23:00:24.765312", + "modified": "2019-07-09 15:07:03.328591", "modified_by": "Administrator", "module": "CRM", "name": "Email Campaign", diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py index 82ee6a6cb4a..1132226b90d 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -5,14 +5,18 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import getdate, add_days, nowdate +from frappe.utils import getdate, add_days, today, nowdate, cstr from frappe.model.document import Document -from frappe.email.inbox import link_communication_to_document +from frappe.core.doctype.communication.email import make class EmailCampaign(Document): def validate(self): self.validate_dates() - self.validate_lead() + #checking if email is set for lead. Not checking for contact as email is a mandatory field for contact. + if self.email_campaign_for == "Lead": + self.validate_lead() + self.set_end_date() + self.update_status() def validate_dates(self): campaign = frappe.get_doc("Campaign", self.campaign_name) @@ -30,37 +34,105 @@ class EmailCampaign(Document): frappe.throw(_("Email Schedule cannot extend Campaign End Date")) def validate_lead(self): - lead = frappe.get_doc("Lead", self.lead) + lead = frappe.get_doc("Lead", self.recipient) if not lead.get("email_id"): - frappe.throw(_("Please set email id for lead communication")) + frappe.throw(_("Please set an email id for lead communication")) - def send(self): - lead = frappe.get_doc("Lead", self.get("lead")) - email_schedule = frappe.get_doc("Campaign Email Schedule", self.get("email_schedule")) - email_template = frappe.get_doc("Email Template", email_schedule.name) - frappe.sendmail( - recipients = lead.get("email_id"), - sender = lead.get("lead_owner"), - subject = email_template.get("subject"), - message = email_template.get("response"), - reference_doctype = self.doctype, - reference_name = self.name - ) + def set_end_date(self): + #set the end date as start date + max(send after days) in email schedule + send_after_days = [] + for entry in self.get("email_schedule"): + send_after_days.append(entry.send_after_days) + self.end_date = add_days(getdate(self.start_date), max(send_after_days)) - def on_submit(self): - """Create a new communication linked to the campaign if not created""" - if not frappe.db.sql("select subject from tabCommunication where reference_name = %s", self.name): - doc = frappe.new_doc("Communication") - doc.subject = "Email Campaign Communication: " + self.name - link_communication_to_document(doc, "Email Campaign", self.name, ignore_communication_links = False) + def update_status(self): + start_date = getdate(self.start_date) + end_date = getdate(self.end_date) + today_date = getdate(today()) + if self.unsubscribed: + self.status = "Unsubscribed" + else: + if start_date > today_date: + self.status = "Scheduled" + elif end_date >= today_date: + self.status = "In Progress" + elif end_date < today_date: + self.status = "Completed" -@frappe.whitelist() +#called through hooks to send campaign mails to leads def send_email_to_leads(): - email_campaigns = frappe.get_all("Email Campaign", filters = { 'start_date': ("<=", nowdate()) }) + email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']), 'unsubscribed': 0 }) for campaign in email_campaigns: email_campaign = frappe.get_doc("Email Campaign", campaign.name) for entry in email_campaign.get("email_schedule"): scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days')) - if(scheduled_date == nowdate()): - email_campaign.send() -# send_email_to_leads() + if scheduled_date == getdate(today()): + send_mail(entry, email_campaign) + +def send_mail(entry, email_campaign): + if email_campaign.email_campaign_for == "Lead": + lead = frappe.get_doc("Lead", email_campaign.get("recipient")) + recipient_email = lead.email_id + elif email_campaign.email_campaign_for == "Contact": + recipient = frappe.get_doc("Contact", email_campaign.get("recipient")) + recipient_email = recipient.email_id + email_template = frappe.get_doc("Email Template", entry.get("email_template")) + sender = frappe.get_doc("User", email_campaign.get("sender")) + sender_email = sender.email + # send mail and link communication to document + comm = make( + doctype = "Email Campaign", + name = email_campaign.name, + subject = email_template.get("subject"), + content = email_template.get("response"), + sender = sender_email, + recipients = recipient_email, + communication_medium = "Email", + sent_or_received = "Sent", + send_email = False, + email_template = email_template.name + ) + frappe.sendmail( + recipients = recipient_email, + sender = sender_email, + subject = email_template.get("subject"), + content = email_template.get("response"), + reference_doctype = "Email Campaign", + reference_name = email_campaign.name, + unsubscribe_method = "/api/method/erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient", + unsubscribe_params = {"name": email_campaign.name, "email": recipient_email}, + unsubscribe_message = "Stop Getting Email Campaign Mails", + communication = comm.get("name") + ) + +@frappe.whitelist(allow_guest=True) +def unsubscribe_recipient(name, email): + # unsubsribe from comments and communications + try: + frappe.get_doc({ + "doctype": "Email Unsubscribe", + "email": email, + "reference_doctype": "Email Campaign", + "reference_name": name + }).insert(ignore_permissions=True) + + except frappe.DuplicateEntryError: + frappe.db.rollback() + + else: + frappe.db.commit() + frappe.db.set_value("Email Campaign", name, "unsubscribed", 1) + frappe.db.set_value("Email Campaign", name, "status", "Unsubscribed") + frappe.db.commit() + return_unsubscribed_page(email, name) + +def return_unsubscribed_page(email, name): + frappe.respond_as_web_page(_("Unsubscribed"), + _("{0} has left the Email Campaign {1}").format(email, name), + indicator_color='green') + +#called through hooks to update email campaign status daily +def set_email_campaign_status(): + email_campaigns = frappe.get_all("Email Campaign") + for email_campaign in email_campaigns: + email_campaign.update_status() diff --git a/erpnext/crm/doctype/email_campaign/email_campaign_list.js b/erpnext/crm/doctype/email_campaign/email_campaign_list.js new file mode 100644 index 00000000000..d1bfdd31ee7 --- /dev/null +++ b/erpnext/crm/doctype/email_campaign/email_campaign_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings['Email Campaign'] = { + get_indicator: function(doc) { + var colors = { + "Unsubscribed": "red", + "Scheduled": "blue", + "In Progress": "orange", + "Completed": "green" + } + return [__(doc.status), colors[doc.status], "status,=," + doc.status]; + } +}; diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1466d243ad6..1b34a59f55b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -266,7 +266,8 @@ scheduler_events = { "erpnext.projects.doctype.project.project.send_project_status_email_to_users", "erpnext.quality_management.doctype.quality_review.quality_review.review", "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status", - "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads" + "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads", + "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status" ], "daily_long": [ "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"