Merge pull request #18715 from Alchez/v12-lead-address-contact

feat: create address and contact after lead creation (develop)
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 122e2b4..0c88d28 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -1,4 +1,4 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext");
@@ -7,57 +7,54 @@
 erpnext.LeadController = frappe.ui.form.Controller.extend({
 	setup: function () {
 		this.frm.make_methods = {
+			'Customer': this.make_customer,
 			'Quotation': this.make_quotation,
-			'Opportunity': this.create_opportunity
-		}
-
-		this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {
-			return { query: "erpnext.controllers.queries.customer_query" }
-		}
+			'Opportunity': this.make_opportunity
+		};
 
 		this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
 	},
 
 	onload: function () {
-		if (cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) {
-			cur_frm.fields_dict.lead_owner.get_query = function (doc, cdt, cdn) {
-				return { query: "frappe.core.doctype.user.user.user_query" }
-			}
-		}
+		this.frm.set_query("customer", function (doc, cdt, cdn) {
+			return { query: "erpnext.controllers.queries.customer_query" }
+		});
 
-		if (cur_frm.fields_dict.contact_by.df.options.match(/^User/)) {
-			cur_frm.fields_dict.contact_by.get_query = function (doc, cdt, cdn) {
-				return { query: "frappe.core.doctype.user.user.user_query" }
-			}
-		}
+		this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
+			return { query: "frappe.core.doctype.user.user.user_query" }
+		});
+
+		this.frm.set_query("contact_by", function (doc, cdt, cdn) {
+			return { query: "frappe.core.doctype.user.user.user_query" }
+		});
 	},
 
 	refresh: function () {
-		var doc = this.frm.doc;
+		let doc = this.frm.doc;
 		erpnext.toggle_naming_series();
 		frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
 
-		if(!doc.__islocal && doc.__onload && !doc.__onload.is_customer) {
-			this.frm.add_custom_button(__("Customer"), this.create_customer, __('Create'));
-			this.frm.add_custom_button(__("Opportunity"), this.create_opportunity, __('Create'));
-			this.frm.add_custom_button(__("Quotation"), this.make_quotation, __('Create'));
+		if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
+			this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
+			this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create"));
+			this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
 		}
 
-		if (!this.frm.doc.__islocal) {
-			frappe.contacts.render_address_and_contact(cur_frm);
+		if (!this.frm.is_new()) {
+			frappe.contacts.render_address_and_contact(this.frm);
 		} else {
-			frappe.contacts.clear_address_and_contact(cur_frm);
+			frappe.contacts.clear_address_and_contact(this.frm);
 		}
 	},
 
-	create_customer: function () {
+	make_customer: function () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_customer",
 			frm: cur_frm
 		})
 	},
 
-	create_opportunity: function () {
+	make_opportunity: function () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_opportunity",
 			frm: cur_frm
@@ -77,7 +74,7 @@
 	},
 
 	company_name: function () {
-		if (this.frm.doc.organization_lead == 1) {
+		if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) {
 			this.frm.set_value("lead_name", this.frm.doc.company_name);
 		}
 	},
@@ -85,7 +82,7 @@
 	contact_date: function () {
 		if (this.frm.doc.contact_date) {
 			let d = moment(this.frm.doc.contact_date);
-			d.add(1, "hours");
+			d.add(1, "day");
 			this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat));
 		}
 	}
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index eb68c67..bc007b1 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_events_in_timeline": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
@@ -16,6 +17,8 @@
   "col_break123",
   "lead_owner",
   "status",
+  "salutation",
+  "designation",
   "gender",
   "source",
   "customer",
@@ -28,17 +31,22 @@
   "ends_on",
   "notes_section",
   "notes",
-  "contact_info",
-  "address_desc",
+  "address_info",
   "address_html",
+  "address_title",
+  "address_line1",
+  "address_line2",
+  "city",
+  "county",
   "column_break2",
   "contact_html",
+  "state",
+  "country",
+  "pincode",
+  "contact_section",
   "phone",
-  "salutation",
   "mobile_no",
   "fax",
-  "website",
-  "territory",
   "more_info",
   "type",
   "market_segment",
@@ -46,8 +54,11 @@
   "request_type",
   "column_break3",
   "company",
+  "website",
+  "territory",
   "unsubscribed",
-  "blog_subscriber"
+  "blog_subscriber",
+  "title"
  ],
  "fields": [
   {
@@ -73,7 +84,6 @@
    "set_only_once": 1
   },
   {
-   "depends_on": "eval:!doc.organization_lead",
    "fieldname": "lead_name",
    "fieldtype": "Data",
    "in_global_search": 1,
@@ -130,7 +140,13 @@
    "search_index": 1
   },
   {
-   "depends_on": "eval:!doc.organization_lead",
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "salutation",
+   "fieldtype": "Link",
+   "label": "Salutation",
+   "options": "Salutation"
+  },
+  {
    "fieldname": "gender",
    "fieldtype": "Link",
    "label": "Gender",
@@ -217,39 +233,73 @@
    "label": "Notes"
   },
   {
-   "collapsible": 1,
-   "fieldname": "contact_info",
-   "fieldtype": "Section Break",
-   "label": "Address & Contact",
-   "oldfieldtype": "Column Break",
-   "options": "fa fa-map-marker"
-  },
-  {
-   "depends_on": "eval:doc.__islocal",
-   "fieldname": "address_desc",
-   "fieldtype": "HTML",
-   "label": "Address Desc",
-   "print_hide": 1
-  },
-  {
    "fieldname": "address_html",
    "fieldtype": "HTML",
    "label": "Address HTML",
    "read_only": 1
   },
   {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "address_title",
+   "fieldtype": "Data",
+   "label": "Address Title"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "address_line1",
+   "fieldtype": "Data",
+   "label": "Address Line 1"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "address_line2",
+   "fieldtype": "Data",
+   "label": "Address Line 2"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "city",
+   "fieldtype": "Data",
+   "label": "City/Town"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "county",
+   "fieldtype": "Data",
+   "label": "County"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "state",
+   "fieldtype": "Data",
+   "label": "State"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "country",
+   "fieldtype": "Link",
+   "label": "Country",
+   "options": "Country"
+  },
+  {
+   "depends_on": "eval: doc.__islocal",
+   "fieldname": "pincode",
+   "fieldtype": "Data",
+   "label": "Postal Code",
+   "options": "Country"
+  },
+  {
    "fieldname": "column_break2",
    "fieldtype": "Column Break"
   },
   {
-   "depends_on": "eval:doc.organization_lead",
    "fieldname": "contact_html",
    "fieldtype": "HTML",
    "label": "Contact HTML",
    "read_only": 1
   },
   {
-   "depends_on": "eval:!doc.organization_lead",
+   "depends_on": "eval: doc.__islocal",
    "fieldname": "phone",
    "fieldtype": "Data",
    "label": "Phone",
@@ -257,14 +307,7 @@
    "oldfieldtype": "Data"
   },
   {
-   "depends_on": "eval:!doc.organization_lead",
-   "fieldname": "salutation",
-   "fieldtype": "Link",
-   "label": "Salutation",
-   "options": "Salutation"
-  },
-  {
-   "depends_on": "eval:!doc.organization_lead",
+   "depends_on": "eval: doc.__islocal",
    "fieldname": "mobile_no",
    "fieldtype": "Data",
    "label": "Mobile No.",
@@ -272,7 +315,7 @@
    "oldfieldtype": "Data"
   },
   {
-   "depends_on": "eval:!doc.organization_lead",
+   "depends_on": "eval: doc.__islocal",
    "fieldname": "fax",
    "fieldtype": "Data",
    "label": "Fax",
@@ -280,22 +323,6 @@
    "oldfieldtype": "Data"
   },
   {
-   "fieldname": "website",
-   "fieldtype": "Data",
-   "label": "Website",
-   "oldfieldname": "website",
-   "oldfieldtype": "Data"
-  },
-  {
-   "fieldname": "territory",
-   "fieldtype": "Link",
-   "label": "Territory",
-   "oldfieldname": "territory",
-   "oldfieldtype": "Link",
-   "options": "Territory",
-   "print_hide": 1
-  },
-  {
    "collapsible": 1,
    "fieldname": "more_info",
    "fieldtype": "Section Break",
@@ -351,6 +378,22 @@
    "remember_last_selected_value": 1
   },
   {
+   "fieldname": "website",
+   "fieldtype": "Data",
+   "label": "Website",
+   "oldfieldname": "website",
+   "oldfieldtype": "Data"
+  },
+  {
+   "fieldname": "territory",
+   "fieldtype": "Link",
+   "label": "Territory",
+   "oldfieldname": "territory",
+   "oldfieldtype": "Link",
+   "options": "Territory",
+   "print_hide": 1
+  },
+  {
    "default": "0",
    "fieldname": "unsubscribed",
    "fieldtype": "Check",
@@ -361,12 +404,42 @@
    "fieldname": "blog_subscriber",
    "fieldtype": "Check",
    "label": "Blog Subscriber"
+  },
+  {
+   "fieldname": "title",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Title",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "designation",
+   "fieldtype": "Link",
+   "label": "Designation",
+   "options": "Designation"
+  },
+  {
+   "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.__islocal",
+   "fieldname": "address_info",
+   "fieldtype": "Section Break",
+   "label": "Address & Contact",
+   "oldfieldtype": "Column Break",
+   "options": "fa fa-map-marker"
+  },
+  {
+   "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.__islocal",
+   "fieldname": "contact_section",
+   "fieldtype": "Section Break",
+   "label": "Contact"
   }
  ],
  "icon": "fa fa-user",
  "idx": 5,
  "image_field": "image",
- "modified": "2019-09-19 12:49:02.536647",
+ "links": [],
+ "modified": "2019-12-24 16:00:44.239168",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Lead",
@@ -438,5 +511,5 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
- "title_field": "lead_name"
+ "title_field": "title"
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 1dae4b9..6cab18dc 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -2,18 +2,19 @@
 # License: GNU General Public License v3. See license.txt
 
 from __future__ import unicode_literals
-import frappe
-from frappe import _
-from frappe.utils import (cstr, validate_email_address, cint, comma_and, has_gravatar, now, getdate, nowdate)
-from frappe.model.mapper import get_mapped_doc
 
-from erpnext.controllers.selling_controller import SellingController
-from frappe.contacts.address_and_contact import load_address_and_contact
+import frappe
 from erpnext.accounts.party import set_taxes
+from erpnext.controllers.selling_controller import SellingController
+from frappe import _
+from frappe.contacts.address_and_contact import load_address_and_contact
 from frappe.email.inbox import link_communication_to_document
+from frappe.model.mapper import get_mapped_doc
+from frappe.utils import cint, comma_and, cstr, getdate, has_gravatar, nowdate, validate_email_address
 
 sender_field = "email_id"
 
+
 class Lead(SellingController):
 	def get_feed(self):
 		return '{0}: {1}'.format(_(self.status), self.lead_name)
@@ -23,15 +24,23 @@
 		self.get("__onload").is_customer = customer
 		load_address_and_contact(self)
 
+	def before_insert(self):
+		self.address_doc = self.create_address()
+		self.contact_doc = self.create_contact()
+
+	def after_insert(self):
+		self.update_links()
+		# after the address and contact are created, flush the field values
+		# to avoid inconsistent reporting in case the documents are changed
+		self.flush_address_and_contact_fields()
+
 	def validate(self):
 		self.set_lead_name()
+		self.set_title()
 		self._prev = frappe._dict({
-			"contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if \
-				(not cint(self.get("__islocal"))) else None,
-			"ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if \
-				(not cint(self.get("__islocal"))) else None,
-			"contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if \
-				(not cint(self.get("__islocal"))) else None,
+			"contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None,
+			"ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None,
+			"contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None,
 		})
 
 		self.set_status()
@@ -39,7 +48,7 @@
 
 		if self.email_id:
 			if not self.flags.ignore_email_validation:
-				validate_email_address(self.email_id, True)
+				validate_email_address(self.email_id, throw=True)
 
 			if self.email_id == self.lead_owner:
 				frappe.throw(_("Lead Owner cannot be same as the Lead"))
@@ -53,8 +62,7 @@
 		if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
 			frappe.throw(_("Next Contact Date cannot be in the past"))
 
-		if self.ends_on and self.contact_date and\
-			(self.ends_on < self.contact_date):
+		if self.ends_on and self.contact_date and (self.ends_on < self.contact_date):
 			frappe.throw(_("Ends On date cannot be before Next Contact Date."))
 
 	def on_update(self):
@@ -66,23 +74,21 @@
 			"starts_on": self.contact_date,
 			"ends_on": self.ends_on or "",
 			"subject": ('Contact ' + cstr(self.lead_name)),
-			"description": ('Contact ' + cstr(self.lead_name)) + \
-				(self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
+			"description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
 		}, force)
 
 	def check_email_id_is_unique(self):
 		if self.email_id:
 			# validate email is unique
-			duplicate_leads = frappe.db.sql_list("""select name from tabLead
-				where email_id=%s and name!=%s""", (self.email_id, self.name))
+			duplicate_leads = frappe.get_all("Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]})
+			duplicate_leads = [lead.name for lead in duplicate_leads]
 
 			if duplicate_leads:
 				frappe.throw(_("Email Address must be unique, already exists for {0}")
 					.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
 
 	def on_trash(self):
-		frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""",
-			self.name)
+		frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
 
 		self.delete_events()
 
@@ -115,10 +121,101 @@
 
 			self.lead_name = self.company_name
 
+	def set_title(self):
+		if self.organization_lead:
+			self.title = self.company_name
+		else:
+			self.title = self.lead_name
+
+	def create_address(self):
+		address_fields = ["address_title", "address_line1", "address_line2",
+			"city", "county", "state", "country", "pincode"]
+		info_fields = ["email_id", "phone", "fax"]
+
+		# do not create an address if no fields are available,
+		# skipping country since the system auto-sets it from system defaults
+		if not any([self.get(field) for field in address_fields if field != "country"]):
+			return
+
+		address = frappe.new_doc("Address")
+		address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
+		address.update({info_field: self.get(info_field) for info_field in info_fields})
+		address.insert()
+
+		return address
+
+	def create_contact(self):
+		if not self.lead_name:
+			self.set_lead_name()
+
+		names = self.lead_name.split(" ")
+		if len(names) > 1:
+			first_name, last_name = names[0], " ".join(names[1:])
+		else:
+			first_name, last_name = self.lead_name, None
+
+		contact = frappe.new_doc("Contact")
+		contact.update({
+			"first_name": first_name,
+			"last_name": last_name,
+			"salutation": self.salutation,
+			"gender": self.gender,
+			"designation": self.designation,
+		})
+
+		if self.email_id:
+			contact.append("email_ids", {
+				"email_id": self.email_id,
+				"is_primary": 1
+			})
+
+		if self.phone:
+			contact.append("phone_nos", {
+				"phone": self.phone,
+				"is_primary": 1
+			})
+
+		if self.mobile_no:
+			contact.append("phone_nos", {
+				"phone": self.mobile_no
+			})
+
+		contact.insert()
+
+		return contact
+
+	def update_links(self):
+		# update address links
+		if self.address_doc:
+			self.address_doc.append("links", {
+				"link_doctype": "Lead",
+				"link_name": self.name,
+				"link_title": self.lead_name
+			})
+			self.address_doc.save()
+
+		# update contact links
+		if self.contact_doc:
+			self.contact_doc.append("links", {
+				"link_doctype": "Lead",
+				"link_name": self.name,
+				"link_title": self.lead_name
+			})
+			self.contact_doc.save()
+
+	def flush_address_and_contact_fields(self):
+		fields = ['address_line1', 'address_line2', 'address_title',
+			'city', 'county', 'country', 'fax', 'pincode', 'state']
+
+		for field in fields:
+			self.set(field, None)
+
+
 @frappe.whitelist()
 def make_customer(source_name, target_doc=None):
 	return _make_customer(source_name, target_doc)
 
+
 def _make_customer(source_name, target_doc=None, ignore_permissions=False):
 	def set_missing_values(source, target):
 		if source.company_name:
@@ -143,6 +240,7 @@
 
 	return doclist
 
+
 @frappe.whitelist()
 def make_opportunity(source_name, target_doc=None):
 	def set_missing_values(source, target):
@@ -164,6 +262,7 @@
 
 	return target_doc
 
+
 @frappe.whitelist()
 def make_quotation(source_name, target_doc=None):
 	def set_missing_values(source, target):
@@ -205,7 +304,8 @@
 
 @frappe.whitelist()
 def get_lead_details(lead, posting_date=None, company=None):
-	if not lead: return {}
+	if not lead:
+		return {}
 
 	from erpnext.accounts.party import set_address_details
 	out = frappe._dict()
@@ -231,6 +331,7 @@
 
 	return out
 
+
 @frappe.whitelist()
 def make_lead_from_communication(communication, ignore_communication_links=False):
 	""" raise a issue from email """
@@ -267,4 +368,4 @@
 
 	lead = leads[0].name if leads else None
 
-	return lead
\ No newline at end of file
+	return lead
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index e26b1c8..ab8e942 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -651,3 +651,4 @@
 erpnext.patches.v12_0.set_production_capacity_in_workstation
 erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order
 erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim
+erpnext.patches.v12_0.set_lead_title_field
diff --git a/erpnext/patches/v12_0/set_lead_title_field.py b/erpnext/patches/v12_0/set_lead_title_field.py
new file mode 100644
index 0000000..86e0003
--- /dev/null
+++ b/erpnext/patches/v12_0/set_lead_title_field.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+	frappe.reload_doc("crm", "doctype", "lead")
+	frappe.db.sql("""
+		UPDATE
+			`tabLead`
+		SET
+			title = IF(organization_lead = 1, company_name, lead_name)
+	""")