Merge branch 'develop' into crm-carry-forward-communication-comments
diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.json b/erpnext/crm/doctype/crm_settings/crm_settings.json
index 8f0fa31..a2a19b9 100644
--- a/erpnext/crm/doctype/crm_settings/crm_settings.json
+++ b/erpnext/crm/doctype/crm_settings/crm_settings.json
@@ -17,7 +17,9 @@
   "column_break_9",
   "create_event_on_next_contact_date_opportunity",
   "quotation_section",
-  "default_valid_till"
+  "default_valid_till",
+  "section_break_13",
+  "carry_forward_communication_and_comments"
  ],
  "fields": [
   {
@@ -85,13 +87,25 @@
    "fieldname": "quotation_section",
    "fieldtype": "Section Break",
    "label": "Quotation"
+  },
+  {
+   "fieldname": "section_break_13",
+   "fieldtype": "Section Break",
+   "label": "Other Settings"
+  },
+  {
+   "default": "0",
+   "description": "All the Comments and Emails will be copied from one document to another newly created document(Lead -> Opportunity -> Quotation) throughout the CRM documents.",
+   "fieldname": "carry_forward_communication_and_comments",
+   "fieldtype": "Check",
+   "label": "Carry Forward Communication and Comments"
   }
  ],
  "icon": "fa fa-cog",
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-11-03 10:00:36.883496",
+ "modified": "2021-12-20 12:51:38.894252",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "CRM Settings",
@@ -105,6 +119,26 @@
    "role": "System Manager",
    "share": 1,
    "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Sales Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "Sales Master Manager",
+   "share": 1,
+   "write": 1
   }
  ],
  "sort_field": "modified",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index fcbd4de..a4fd765 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -11,6 +11,7 @@
 from frappe.query_builder import DocType
 from frappe.utils import cint, cstr, flt, get_fullname
 
+from erpnext.crm.utils import add_link_in_communication, copy_comments
 from erpnext.setup.utils import get_exchange_rate
 from erpnext.utilities.transaction_base import TransactionBase
 
@@ -20,6 +21,11 @@
 		if self.opportunity_from == "Lead":
 			frappe.get_doc("Lead", self.party_name).set_status(update=True)
 
+		if self.opportunity_from in ["Lead", "Prospect"]:
+			if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"):
+				copy_comments(self.opportunity_from, self.party_name, self)
+				add_link_in_communication(self.opportunity_from, self.party_name, self)
+
 	def validate(self):
 		self._prev = frappe._dict({
 			"contact_date": frappe.db.get_value("Opportunity", self.name, "contact_date") if \
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 6e6fed5..db44b6a 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -4,10 +4,12 @@
 import unittest
 
 import frappe
-from frappe.utils import random_string, today
+from frappe.utils import now_datetime, random_string, today
 
 from erpnext.crm.doctype.lead.lead import make_customer
+from erpnext.crm.doctype.lead.test_lead import make_lead
 from erpnext.crm.doctype.opportunity.opportunity import make_quotation
+from erpnext.crm.utils import get_linked_communication_list
 
 test_records = frappe.get_test_records('Opportunity')
 
@@ -28,21 +30,11 @@
 		self.assertEqual(doc.status, "Quotation")
 
 	def test_make_new_lead_if_required(self):
-		new_lead_email_id = "new{}@example.com".format(random_string(5))
-		args = {
-			"doctype": "Opportunity",
-			"contact_email": new_lead_email_id,
-			"opportunity_type": "Sales",
-			"with_items": 0,
-			"transaction_date": today()
-		}
-		# new lead should be created against the new.opportunity@example.com
-		opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
+		opp_doc = make_opportunity_from_lead()
 
 		self.assertTrue(opp_doc.party_name)
 		self.assertEqual(opp_doc.opportunity_from, "Lead")
-		self.assertEqual(frappe.db.get_value("Lead", opp_doc.party_name, "email_id"),
-			new_lead_email_id)
+		self.assertEqual(frappe.db.get_value("Lead", opp_doc.party_name, "email_id"), opp_doc.contact_email)
 
 		# create new customer and create new contact against 'new.opportunity@example.com'
 		customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
@@ -54,18 +46,60 @@
 				"link_name": customer.name
 			}]
 		})
-		contact.add_email(new_lead_email_id, is_primary=True)
+		contact.add_email(opp_doc.contact_email, is_primary=True)
 		contact.insert(ignore_permissions=True)
 
-		opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
-		self.assertTrue(opp_doc.party_name)
-		self.assertEqual(opp_doc.opportunity_from, "Customer")
-		self.assertEqual(opp_doc.party_name, customer.name)
-
 	def test_opportunity_item(self):
 		opportunity_doc = make_opportunity(with_items=1, rate=1100, qty=2)
 		self.assertEqual(opportunity_doc.total, 2200)
 
+	def test_carry_forward_of_email_and_comments(self):
+		frappe.db.set_value("CRM Settings", "CRM Settings", "carry_forward_communication_and_comments", 1)
+		lead_doc = make_lead()
+		lead_doc.add_comment('Comment', text='Test Comment 1')
+		lead_doc.add_comment('Comment', text='Test Comment 2')
+		create_communication(lead_doc.doctype, lead_doc.name, lead_doc.email_id)
+		create_communication(lead_doc.doctype, lead_doc.name, lead_doc.email_id)
+
+		opp_doc = make_opportunity(opportunity_from="Lead", lead=lead_doc.name)
+		opportunity_comment_count = frappe.db.count("Comment", {"reference_doctype": opp_doc.doctype, "reference_name": opp_doc.name})
+		opportunity_communication_count = len(get_linked_communication_list(opp_doc.doctype, opp_doc.name))
+		self.assertEqual(opportunity_comment_count, 2)
+		self.assertEqual(opportunity_communication_count, 2)
+
+		opp_doc.add_comment('Comment', text='Test Comment 3')
+		opp_doc.add_comment('Comment', text='Test Comment 4')
+		create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email)
+		create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email)
+
+		quotation_doc = make_quotation(opp_doc.name)
+		quotation_doc.append('items', {
+			"item_code": "_Test Item",
+			"qty": 1
+		})
+		quotation_doc.run_method("set_missing_values")
+		quotation_doc.run_method("calculate_taxes_and_totals")
+		quotation_doc.save()
+
+		quotation_comment_count = frappe.db.count("Comment", {"reference_doctype": quotation_doc.doctype, "reference_name": quotation_doc.name, "comment_type": "Comment"})
+		quotation_communication_count = len(get_linked_communication_list(quotation_doc.doctype, quotation_doc.name))
+		self.assertEqual(quotation_comment_count, 4)
+		self.assertEqual(quotation_communication_count, 4)
+
+def make_opportunity_from_lead():
+	new_lead_email_id = "new{}@example.com".format(random_string(5))
+	args = {
+		"doctype": "Opportunity",
+		"contact_email": new_lead_email_id,
+		"opportunity_type": "Sales",
+		"with_items": 0,
+		"transaction_date": today()
+	}
+	# new lead should be created against the new.opportunity@example.com
+	opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
+
+	return opp_doc
+
 def make_opportunity(**args):
 	args = frappe._dict(args)
 
@@ -95,3 +129,20 @@
 
 	opp_doc.insert()
 	return opp_doc
+
+def create_communication(reference_doctype, reference_name, sender, sent_or_received=None, creation=None):
+	communication = frappe.get_doc({
+		"doctype": "Communication",
+		"communication_type": "Communication",
+		"communication_medium": "Email",
+		"sent_or_received": sent_or_received or "Sent",
+		"email_status": "Open",
+		"subject": "Test Subject",
+		"sender": sender,
+		"content": "Test",
+		"status": "Linked",
+		"reference_doctype": reference_doctype,
+		"creation": creation or now_datetime(),
+		"reference_name": reference_name
+	})
+	communication.save()
\ No newline at end of file
diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py
index 367aa3d..cc4c1d3 100644
--- a/erpnext/crm/doctype/prospect/prospect.py
+++ b/erpnext/crm/doctype/prospect/prospect.py
@@ -6,6 +6,8 @@
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
 
+from erpnext.crm.utils import add_link_in_communication, copy_comments
+
 
 class Prospect(Document):
 	def onload(self):
@@ -20,6 +22,12 @@
 	def on_trash(self):
 		self.unlink_dynamic_links()
 
+	def after_insert(self):
+		if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"):
+			for row in self.get('prospect_lead'):
+				copy_comments("Lead", row.lead, self)
+				add_link_in_communication("Lead", row.lead, self)
+
 	def update_lead_details(self):
 		for row in self.get('prospect_lead'):
 			lead = frappe.get_value('Lead', row.lead, ['lead_name', 'status', 'email_id', 'mobile_no'], as_dict=True)
diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py
index 95b19ec..a4576a2 100644
--- a/erpnext/crm/utils.py
+++ b/erpnext/crm/utils.py
@@ -21,3 +21,30 @@
 			lead = frappe.get_doc("Lead", contact_lead)
 			lead.db_set("phone", phone)
 			lead.db_set("mobile_no", mobile_no)
+
+def copy_comments(doctype, docname, doc):
+	comments = frappe.db.get_values("Comment", filters={"reference_doctype": doctype, "reference_name": docname, "comment_type": "Comment"}, fieldname="*")
+	for comment in comments:
+		comment = frappe.get_doc(comment.update({"doctype":"Comment"}))
+		comment.name = None
+		comment.reference_doctype = doc.doctype
+		comment.reference_name = doc.name
+		comment.insert()
+
+def add_link_in_communication(doctype, docname, doc):
+	communication_list = get_linked_communication_list(doctype, docname)
+
+	for communication in communication_list:
+		communication_doc = frappe.get_doc("Communication", communication)
+		communication_doc.add_link(doc.doctype, doc.name, autosave=True)
+
+def get_linked_communication_list(doctype, docname):
+	communications = frappe.get_all("Communication", filters={"reference_doctype": doctype, "reference_name": docname}, pluck='name')
+	communication_links = frappe.get_all('Communication Link',
+		{
+			"link_doctype": doctype,
+			"link_name": docname,
+			"parent": ("not in", communications)
+		}, pluck="parent")
+
+	return communications + communication_links
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index c4752ae..daab6fb 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -8,6 +8,7 @@
 from frappe.utils import flt, getdate, nowdate
 
 from erpnext.controllers.selling_controller import SellingController
+from erpnext.crm.utils import add_link_in_communication, copy_comments
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -34,6 +35,16 @@
 		from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
 		make_packing_list(self)
 
+	def after_insert(self):
+		if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"):
+			if self.opportunity:
+				copy_comments("Opportunity", self.opportunity, self)
+				add_link_in_communication("Opportunity", self.opportunity, self)
+
+			elif self.quotation_to == "Lead" and self.party_name:
+				copy_comments("Lead", self.party_name, self)
+				add_link_in_communication("Lead", self.party_name, self)
+
 	def validate_valid_till(self):
 		if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
 			frappe.throw(_("Valid till date cannot be before transaction date"))