merged
diff --git a/erpnext/crm/doctype/campaign_email_schedule/__init__.py b/erpnext/crm/doctype/campaign_email_schedule/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/campaign_email_schedule/__init__.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
new file mode 100644
index 0000000..2d90094
--- /dev/null
+++ b/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.json
@@ -0,0 +1,38 @@
+{
+ "creation": "2019-06-30 15:56:20.306901",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "send_after_days",
+ "email_template"
+ ],
+ "fields": [
+ {
+ "fieldname": "send_after_days",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Send After (days)",
+ "reqd": 1
+ },
+ {
+ "fieldname": "email_template",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Email Template",
+ "options": "Email Template",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "modified": "2019-06-30 15:56:20.306901",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Campaign Email Schedule",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.py b/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.py
new file mode 100644
index 0000000..8445b8a
--- /dev/null
+++ b/erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class CampaignEmailSchedule(Document):
+ pass
diff --git a/erpnext/crm/doctype/email_campaign/__init__.py b/erpnext/crm/doctype/email_campaign/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/__init__.py
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.js b/erpnext/crm/doctype/email_campaign/email_campaign.js
new file mode 100644
index 0000000..6020028
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Email Campaign', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.json b/erpnext/crm/doctype/email_campaign/email_campaign.json
new file mode 100644
index 0000000..66b3546
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.json
@@ -0,0 +1,129 @@
+{
+ "autoname": "naming_series:",
+ "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",
+ "end_date",
+ "status",
+ "email_schedule_section",
+ "email_schedule",
+ "unsubscribed",
+ "naming_series"
+ ],
+ "fields": [
+ {
+ "fieldname": "campaign_section",
+ "fieldtype": "Section Break",
+ "label": "Campaign"
+ },
+ {
+ "fieldname": "campaign_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Campaign Name",
+ "options": "Campaign",
+ "reqd": 1
+ },
+ {
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "label": "Status",
+ "options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "start_date",
+ "fieldtype": "Date",
+ "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",
+ "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"
+ }
+ ],
+ "modified": "2019-07-09 15:07:03.328591",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Email Campaign",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/email_campaign/email_campaign.py b/erpnext/crm/doctype/email_campaign/email_campaign.py
new file mode 100644
index 0000000..1132226
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import getdate, add_days, today, nowdate, cstr
+from frappe.model.document import Document
+from frappe.core.doctype.communication.email import make
+
+class EmailCampaign(Document):
+ def validate(self):
+ self.validate_dates()
+ #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)
+
+ #email campaign cannot start before campaign
+ 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):
+ frappe.throw(_("Email Schedule cannot extend Campaign End Date"))
+
+ def validate_lead(self):
+ lead = frappe.get_doc("Lead", self.recipient)
+ if not lead.get("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"
+
+#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"):
+ 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
+ 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 0000000..d1bfdd3
--- /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/crm/doctype/email_campaign/test_email_campaign.py b/erpnext/crm/doctype/email_campaign/test_email_campaign.py
new file mode 100644
index 0000000..f5eab48
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/test_email_campaign.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestEmailCampaign(unittest.TestCase):
+ pass
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index d814700..e7a4bc4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -272,6 +272,8 @@
"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.set_email_campaign_status"
],
"daily_long": [
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"