Merge branch 'develop' of https://github.com/frappe/erpnext into develop
diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py
index e49fc60..70784f3 100644
--- a/erpnext/config/crm.py
+++ b/erpnext/config/crm.py
@@ -143,6 +143,11 @@
 				},
 				{
 					"type": "doctype",
+					"name": "Email Campaign",
+					"description": _("Sends Mails to lead or contact based on a Campaign schedule"),
+				},
+				{
+					"type": "doctype",
 					"name": "SMS Center",
 					"description":_("Send mass SMS to your contacts"),
 				},
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index d8c50b2..b2057ca 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -40,7 +40,6 @@
 		["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
 		["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
 		["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
-		["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
 		["Cancelled", "eval:self.docstatus==2"],
 		["Closed", "eval:self.status=='Closed'"],
 		["On Hold", "eval:self.status=='On Hold'"],
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..1481a32
--- /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": [
+  "email_template",
+  "send_after_days"
+ ],
+ "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-07-12 11:46:43.184123",
+ "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..b0e9353
--- /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', {
+	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
new file mode 100644
index 0000000..3259136
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.json
@@ -0,0 +1,95 @@
+{
+ "autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
+ "creation": "2019-06-30 16:05:30.015615",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "campaign_name",
+  "email_campaign_for",
+  "recipient",
+  "sender",
+  "column_break_4",
+  "start_date",
+  "end_date",
+  "status"
+ ],
+ "fields": [
+  {
+   "fieldname": "campaign_name",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Campaign",
+   "options": "Campaign",
+   "reqd": 1
+  },
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "label": "Status",
+   "options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "start_date",
+   "fieldtype": "Date",
+   "label": "Start Date",
+   "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"
+  }
+ ],
+ "modified": "2019-07-12 13:47:37.261213",
+ "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..98e4927
--- /dev/null
+++ b/erpnext/crm/doctype/email_campaign/email_campaign.py
@@ -0,0 +1,102 @@
+# -*- 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.set_date()
+		#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.validate_email_campaign_already_exists()
+		self.update_status()
+
+	def set_date(self):
+		if getdate(self.start_date) < getdate(today()):
+			frappe.throw(_("Start Date cannot be before the current date"))
+		#set the end date as start date + max(send after days) in campaign schedule
+		send_after_days = []
+		campaign = frappe.get_doc("Campaign", self.campaign_name)
+		for entry in campaign.get("campaign_schedules"):
+			send_after_days.append(entry.send_after_days)
+		try:
+			end_date = add_days(getdate(self.start_date), max(send_after_days))
+		except ValueError:
+			frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
+
+	def validate_lead(self):
+		lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id')
+		if not lead_email_id:
+			lead_name = frappe.db.get_value("Lead", self.recipient, 'lead_name')
+			frappe.throw(_("Please set an email id for the Lead {0}").format(lead_name))
+
+	def validate_email_campaign_already_exists(self):
+		email_campaign_exists = frappe.db.exists("Email Campaign", {
+			"campaign_name": self.campaign_name,
+			"recipient": self.recipient,
+			"status": ("in", ["In Progress", "Scheduled"])
+		})
+		if email_campaign_exists:
+			frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
+
+	def update_status(self):
+		start_date = getdate(self.start_date)
+		end_date = getdate(self.end_date)
+		today_date = getdate(today())
+		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_or_contacts():
+	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_cached_doc("Campaign", email_campaign.campaign_name)
+		for entry in campaign.get("campaign_schedules"):
+			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):
+	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.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,
+		recipients = recipient,
+		communication_medium = "Email",
+		sent_or_received = "Sent",
+		send_email = True,
+		email_template = email_template.name
+	)
+	return comm
+
+#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", 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/crm/doctype/email_campaign/email_campaign_list.js b/erpnext/crm/doctype/email_campaign/email_campaign_list.js
new file mode 100644
index 0000000..adc399d
--- /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..47d1a68 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -233,6 +233,9 @@
 	},
 	"Contact":{
 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
+	},
+	"Email Unsubscribe": {
+		"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
 	}
 }
 
@@ -272,6 +275,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_or_contacts",
+		"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"
diff --git a/erpnext/selling/doctype/campaign/campaign.json b/erpnext/selling/doctype/campaign/campaign.json
index d120699..986ac13 100644
--- a/erpnext/selling/doctype/campaign/campaign.json
+++ b/erpnext/selling/doctype/campaign/campaign.json
@@ -6,18 +6,13 @@
  "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",
   "naming_series",
-  "from_date",
-  "column_break1",
-  "status",
-  "to_date",
-  "budget_section",
-  "currency",
-  "column_break2",
-  "budget",
+  "campaign_schedules_section",
+  "campaign_schedules",
   "description_section",
   "description"
  ],
@@ -53,56 +48,24 @@
    "width": "300px"
   },
   {
-   "fieldname": "status",
-   "fieldtype": "Select",
-   "in_list_view": 1,
-   "label": "Status",
-   "options": "\nPlanned\nIn Progress\nCompleted\nCancelled",
-   "reqd": 1,
-   "default": "Planned"
-  },
-  {
-   "fieldname": "from_date",
-   "fieldtype": "Date",
-   "label": "From Date"
-  },
-  {
-   "fieldname": "to_date",
-   "fieldtype": "Date",
-   "label": "To Date"
-  },
-  {
-   "fieldname": "column_break1",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "budget",
-   "fieldtype": "Currency",
-   "label": "Budget"
-  },
-  {
    "fieldname": "description_section",
    "fieldtype": "Section Break"
   },
   {
-   "fieldname": "currency",
-   "fieldtype": "Link",
-   "label": "Currency",
-   "options": "Currency"
+   "fieldname": "campaign_schedules",
+   "fieldtype": "Table",
+   "label": "Campaign Schedules",
+   "options": "Campaign Email Schedule"
   },
   {
-   "fieldname": "column_break2",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "budget_section",
+   "fieldname": "campaign_schedules_section",
    "fieldtype": "Section Break",
-   "label": "BUDGET"
+   "label": "Campaign Schedules"
   }
  ],
  "icon": "fa fa-bullhorn",
  "idx": 1,
- "modified": "2019-04-29 22:09:39.251884",
+ "modified": "2019-07-22 12:03:39.832342",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Campaign",
@@ -140,5 +103,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 0000000..a9d8eca
--- /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']
+			}
+		],
+	}