feat: Email Campaign

This commit is contained in:
Rucha Mahabal
2019-07-12 13:56:36 +05:30
parent 191a0bd43c
commit 9e35bff55c
7 changed files with 93 additions and 129 deletions

View File

@@ -4,8 +4,8 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"send_after_days", "email_template",
"email_template" "send_after_days"
], ],
"fields": [ "fields": [
{ {
@@ -25,7 +25,7 @@
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-06-30 15:56:20.306901", "modified": "2019-07-12 11:46:43.184123",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Campaign Email Schedule", "name": "Campaign Email Schedule",

View File

@@ -5,4 +5,7 @@ frappe.ui.form.on('Email Campaign', {
// refresh: function(frm) { // refresh: function(frm) {
// } // }
email_campaign_for: function(frm) {
frm.set_value('recipient', '');
}
}); });

View File

@@ -1,35 +1,25 @@
{ {
"autoname": "naming_series:", "autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
"creation": "2019-06-30 16:05:30.015615", "creation": "2019-06-30 16:05:30.015615",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"campaign_section",
"campaign_name", "campaign_name",
"email_campaign_for", "email_campaign_for",
"start_date",
"column_break_4",
"sender",
"recipient", "recipient",
"sender",
"column_break_4",
"start_date",
"end_date", "end_date",
"status", "status"
"email_schedule_section",
"email_schedule",
"unsubscribed",
"naming_series"
], ],
"fields": [ "fields": [
{
"fieldname": "campaign_section",
"fieldtype": "Section Break",
"label": "Campaign"
},
{ {
"fieldname": "campaign_name", "fieldname": "campaign_name",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Campaign Name", "label": "Campaign",
"options": "Campaign", "options": "Campaign",
"reqd": 1 "reqd": 1
}, },
@@ -37,7 +27,8 @@
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Status", "label": "Status",
"options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed" "options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed",
"read_only": 1
}, },
{ {
"fieldname": "column_break_4", "fieldname": "column_break_4",
@@ -49,25 +40,6 @@
"label": "Start Date", "label": "Start Date",
"reqd": 1 "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", "fieldname": "end_date",
"fieldtype": "Date", "fieldtype": "Date",
@@ -95,15 +67,9 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Sender", "label": "Sender",
"options": "User" "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", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Email Campaign", "name": "Email Campaign",

View File

@@ -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. #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": if self.email_campaign_for == "Lead":
self.validate_lead() self.validate_lead()
self.set_end_date()
self.update_status() self.update_status()
def validate_dates(self): def validate_dates(self):
@@ -25,114 +24,73 @@ class EmailCampaign(Document):
if campaign.from_date and getdate(self.start_date) < getdate(campaign.from_date): 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")) frappe.throw(_("Email Campaign Start Date cannot be before Campaign Start Date"))
#check if email_schedule is exceeding the campaign end date #set the end date as start date + max(send after days) in campaign schedule
no_of_days = 0 send_after_days = []
for entry in self.get("email_schedule"): for entry in campaign.get("campaign_schedule"):
no_of_days += entry.send_after_days send_after_days.append(entry.send_after_days)
email_schedule_end_date = add_days(getdate(self.start_date), no_of_days) end_date = add_days(getdate(self.start_date), max(send_after_days))
if campaign.to_date and getdate(email_schedule_end_date) > getdate(campaign.to_date):
if campaign.to_date and getdate(end_date) > getdate(campaign.to_date):
frappe.throw(_("Email Schedule cannot extend Campaign End Date")) frappe.throw(_("Email Schedule cannot extend Campaign End Date"))
else:
self.end_date = end_date
def validate_lead(self): def validate_lead(self):
lead = frappe.get_doc("Lead", self.recipient) lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id')
if not lead.get("email_id"): if not lead_email_id:
frappe.throw(_("Please set an email id for lead communication")) 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): def update_status(self):
start_date = getdate(self.start_date) start_date = getdate(self.start_date)
end_date = getdate(self.end_date) end_date = getdate(self.end_date)
today_date = getdate(today()) today_date = getdate(today())
if self.unsubscribed: if start_date > today_date:
self.status = "Unsubscribed" self.status = "Scheduled"
else: elif end_date >= today_date:
if start_date > today_date: self.status = "In Progress"
self.status = "Scheduled" elif end_date < today_date:
elif end_date >= today_date: self.status = "Completed"
self.status = "In Progress"
elif end_date < today_date:
self.status = "Completed"
#called through hooks to send campaign mails to leads #called through hooks to send campaign mails to leads
def send_email_to_leads(): def send_email_to_leads():
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']), 'unsubscribed': 0 }) email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']) })
for campaign in email_campaigns: for camp in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", campaign.name) email_campaign = frappe.get_doc("Email Campaign", camp.name)
for entry in email_campaign.get("email_schedule"): 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')) scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days'))
if scheduled_date == getdate(today()): if scheduled_date == getdate(today()):
send_mail(entry, email_campaign) send_mail(entry, email_campaign)
def send_mail(entry, email_campaign): def send_mail(entry, email_campaign):
if email_campaign.email_campaign_for == "Lead": recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id')
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")) email_template = frappe.get_doc("Email Template", entry.get("email_template"))
sender = frappe.get_doc("User", email_campaign.get("sender")) sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
sender_email = sender.email
# send mail and link communication to document # send mail and link communication to document
comm = make( comm = make(
doctype = "Email Campaign", doctype = "Email Campaign",
name = email_campaign.name, name = email_campaign.name,
subject = email_template.get("subject"), subject = email_template.get("subject"),
content = email_template.get("response"), content = email_template.get("response"),
sender = sender_email, sender = sender,
recipients = recipient_email, recipients = recipient,
communication_medium = "Email", communication_medium = "Email",
sent_or_received = "Sent", sent_or_received = "Sent",
send_email = False, send_email = True,
email_template = email_template.name 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) @frappe.whitelist(allow_guest=True)
def unsubscribe_recipient(name, email): #called from hooks on doc_event Email Unsubscribe
# unsubsribe from comments and communications def unsubscribe_recipient(unsubscribe, method):
try: if unsubscribe.reference_doctype == 'Email Campaign':
frappe.get_doc({ frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed")
"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 #called through hooks to update email campaign status daily
def set_email_campaign_status(): def set_email_campaign_status():
email_campaigns = frappe.get_all("Email Campaign") email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('!=', 'Unsubscribed')})
for email_campaign in email_campaigns: for entry in email_campaigns:
email_campaign = frappe.get_doc("Email Campaign", entry.name)
email_campaign.update_status() email_campaign.update_status()

View File

@@ -233,6 +233,9 @@ doc_events = {
}, },
"Contact":{ "Contact":{
"on_trash": "erpnext.support.doctype.issue.issue.update_issue" "on_trash": "erpnext.support.doctype.issue.issue.update_issue"
},
"Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
} }
} }

View File

@@ -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. ", "description": "Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"engine": "InnoDB",
"field_order": [ "field_order": [
"campaign", "campaign",
"campaign_name", "campaign_name",
@@ -18,6 +19,9 @@
"currency", "currency",
"column_break2", "column_break2",
"budget", "budget",
"schedule_section",
"campaign_schedule_section",
"campaign_schedule",
"description_section", "description_section",
"description" "description"
], ],
@@ -53,13 +57,13 @@
"width": "300px" "width": "300px"
}, },
{ {
"default": "Planned",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"label": "Status", "label": "Status",
"options": "\nPlanned\nIn Progress\nCompleted\nCancelled", "options": "\nPlanned\nIn Progress\nCompleted\nCancelled",
"reqd": 1, "reqd": 1
"default": "Planned"
}, },
{ {
"fieldname": "from_date", "fieldname": "from_date",
@@ -98,11 +102,26 @@
"fieldname": "budget_section", "fieldname": "budget_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "BUDGET" "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", "icon": "fa fa-bullhorn",
"idx": 1, "idx": 1,
"modified": "2019-04-29 22:09:39.251884", "modified": "2019-07-12 11:52:47.196736",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Campaign", "name": "Campaign",
@@ -140,5 +159,7 @@
"write": 1 "write": 1
} }
], ],
"quick_entry": 1 "quick_entry": 1,
} "sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -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']
}
],
}