From 9e35bff55c2fc64bc269582fc0f3c8c2a51823bd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 12 Jul 2019 13:56:36 +0530 Subject: [PATCH] feat: Email Campaign --- .../campaign_email_schedule.json | 6 +- .../doctype/email_campaign/email_campaign.js | 3 + .../email_campaign/email_campaign.json | 52 ++------ .../doctype/email_campaign/email_campaign.py | 114 ++++++------------ erpnext/hooks.py | 3 + .../selling/doctype/campaign/campaign.json | 31 ++++- .../doctype/campaign/campaign_dashboard.py | 13 ++ 7 files changed, 93 insertions(+), 129 deletions(-) create mode 100644 erpnext/selling/doctype/campaign/campaign_dashboard.py diff --git a/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.json b/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.json index 2d900940a3..1481a32d5b 100644 --- a/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.json +++ b/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.json @@ -4,8 +4,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "send_after_days", - "email_template" + "email_template", + "send_after_days" ], "fields": [ { @@ -25,7 +25,7 @@ } ], "istable": 1, - "modified": "2019-06-30 15:56:20.306901", + "modified": "2019-07-12 11:46:43.184123", "modified_by": "Administrator", "module": "CRM", "name": "Campaign Email Schedule", diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.js b/erpnext/crm/doctype/email_campaign/email_campaign.js index 6020028a13..09ed84882d 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.js +++ b/erpnext/crm/doctype/email_campaign/email_campaign.js @@ -5,4 +5,7 @@ frappe.ui.form.on('Email Campaign', { // refresh: function(frm) { // } + email_campaign_for: function(frm) { + frm.set_value('recipient', ''); + } }); diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json index 66b35467cf..3259136275 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.json +++ b/erpnext/crm/doctype/email_campaign/email_campaign.json @@ -1,35 +1,25 @@ { - "autoname": "naming_series:", + "autoname": "format:MAIL-CAMP-{YYYY}-{#####}", "creation": "2019-06-30 16:05:30.015615", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "campaign_section", "campaign_name", "email_campaign_for", - "start_date", - "column_break_4", - "sender", "recipient", + "sender", + "column_break_4", + "start_date", "end_date", - "status", - "email_schedule_section", - "email_schedule", - "unsubscribed", - "naming_series" + "status" ], "fields": [ - { - "fieldname": "campaign_section", - "fieldtype": "Section Break", - "label": "Campaign" - }, { "fieldname": "campaign_name", "fieldtype": "Link", "in_list_view": 1, - "label": "Campaign Name", + "label": "Campaign", "options": "Campaign", "reqd": 1 }, @@ -37,7 +27,8 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed" + "options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed", + "read_only": 1 }, { "fieldname": "column_break_4", @@ -49,25 +40,6 @@ "label": "Start Date", "reqd": 1 }, - { - "fieldname": "email_schedule_section", - "fieldtype": "Section Break", - "label": "Email Schedule" - }, - { - "fieldname": "email_schedule", - "fieldtype": "Table", - "label": "Email Schedule", - "options": "Campaign Email Schedule", - "reqd": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Naming Series", - "options": "MAIL-CAMP-.YYYY.-", - "reqd": 1 - }, { "fieldname": "end_date", "fieldtype": "Date", @@ -95,15 +67,9 @@ "fieldtype": "Link", "label": "Sender", "options": "User" - }, - { - "default": "0", - "fieldname": "unsubscribed", - "fieldtype": "Check", - "label": "Unsubscribed" } ], - "modified": "2019-07-09 15:07:03.328591", + "modified": "2019-07-12 13:47:37.261213", "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 1132226b90..005c2b8185 100644 --- a/erpnext/crm/doctype/email_campaign/email_campaign.py +++ b/erpnext/crm/doctype/email_campaign/email_campaign.py @@ -15,7 +15,6 @@ class EmailCampaign(Document): #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): @@ -25,114 +24,73 @@ class EmailCampaign(Document): if campaign.from_date and getdate(self.start_date) < getdate(campaign.from_date): frappe.throw(_("Email Campaign Start Date cannot be before Campaign Start Date")) - #check if email_schedule is exceeding the campaign end date - no_of_days = 0 - for entry in self.get("email_schedule"): - no_of_days += entry.send_after_days - email_schedule_end_date = add_days(getdate(self.start_date), no_of_days) - if campaign.to_date and getdate(email_schedule_end_date) > getdate(campaign.to_date): + #set the end date as start date + max(send after days) in campaign schedule + send_after_days = [] + for entry in campaign.get("campaign_schedule"): + send_after_days.append(entry.send_after_days) + end_date = add_days(getdate(self.start_date), max(send_after_days)) + + if campaign.to_date and getdate(end_date) > getdate(campaign.to_date): frappe.throw(_("Email Schedule cannot extend Campaign End Date")) + else: + self.end_date = end_date def validate_lead(self): - lead = frappe.get_doc("Lead", self.recipient) - if not lead.get("email_id"): + lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id') + if not lead_email_id: frappe.throw(_("Please set an email id for lead communication")) - 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 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" + 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" #called through hooks to send campaign mails to leads def send_email_to_leads(): - 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"): + email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']) }) + for camp in email_campaigns: + email_campaign = frappe.get_doc("Email Campaign", camp.name) + campaign = frappe.get_doc("Campaign", email_campaign.campaign_name) + for entry in campaign.get("campaign_schedule"): scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days')) 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 + recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("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 + sender = frappe.db.get_value("User", email_campaign.get("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, + sender = sender, + recipients = recipient, communication_medium = "Email", sent_or_received = "Sent", - send_email = False, + send_email = True, 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 from hooks on doc_event Email Unsubscribe +def unsubscribe_recipient(unsubscribe, method): + if unsubscribe.reference_doctype == 'Email Campaign': + frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed") #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_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('!=', 'Unsubscribed')}) + for entry in email_campaigns: + email_campaign = frappe.get_doc("Email Campaign", entry.name) email_campaign.update_status() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e7a4bc4d14..48d133fdff 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -233,6 +233,9 @@ doc_events = { }, "Contact":{ "on_trash": "erpnext.support.doctype.issue.issue.update_issue" + }, + "Email Unsubscribe": { + "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" } } diff --git a/erpnext/selling/doctype/campaign/campaign.json b/erpnext/selling/doctype/campaign/campaign.json index d12069959c..371a9d580d 100644 --- a/erpnext/selling/doctype/campaign/campaign.json +++ b/erpnext/selling/doctype/campaign/campaign.json @@ -6,6 +6,7 @@ "description": "Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "campaign", "campaign_name", @@ -18,6 +19,9 @@ "currency", "column_break2", "budget", + "schedule_section", + "campaign_schedule_section", + "campaign_schedule", "description_section", "description" ], @@ -53,13 +57,13 @@ "width": "300px" }, { + "default": "Planned", "fieldname": "status", "fieldtype": "Select", "in_list_view": 1, "label": "Status", "options": "\nPlanned\nIn Progress\nCompleted\nCancelled", - "reqd": 1, - "default": "Planned" + "reqd": 1 }, { "fieldname": "from_date", @@ -98,11 +102,26 @@ "fieldname": "budget_section", "fieldtype": "Section Break", "label": "BUDGET" + }, + { + "fieldname": "campaign_schedule_section", + "fieldtype": "Section Break", + "label": "Campaign Schedule" + }, + { + "fieldname": "campaign_schedule", + "fieldtype": "Table", + "label": "Campaign Schedule", + "options": "Campaign Email Schedule" + }, + { + "fieldname": "schedule_section", + "fieldtype": "Section Break" } ], "icon": "fa fa-bullhorn", "idx": 1, - "modified": "2019-04-29 22:09:39.251884", + "modified": "2019-07-12 11:52:47.196736", "modified_by": "Administrator", "module": "Selling", "name": "Campaign", @@ -140,5 +159,7 @@ "write": 1 } ], - "quick_entry": 1 -} + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/selling/doctype/campaign/campaign_dashboard.py b/erpnext/selling/doctype/campaign/campaign_dashboard.py new file mode 100644 index 0000000000..a9d8eca38c --- /dev/null +++ b/erpnext/selling/doctype/campaign/campaign_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'campaign_name', + 'transactions': [ + { + 'label': _('Email Campaigns'), + 'items': ['Email Campaign'] + } + ], + }