New Report Address and Contacts (#14307)

* New Report Address and Contacts

* Fix codacy

* Add links to report in module pages
diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py
index 636c160..a534421 100644
--- a/erpnext/config/accounts.py
+++ b/erpnext/config/accounts.py
@@ -459,6 +459,12 @@
 					"is_query_report": True,
 					"name": "Sales Payment Summary",
 					"doctype": "Sales Invoice"
+				},
+				{
+					"type": "report",
+					"is_query_report": True,
+					"name": "Address And Contacts",
+					"doctype": "Address"
 				}
 			]
 		},
diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py
index e20d514..270519e 100644
--- a/erpnext/config/buying.py
+++ b/erpnext/config/buying.py
@@ -198,13 +198,13 @@
 				{
 					"type": "report",
 					"is_query_report": True,
-					"name": "Addresses And Contacts",
+					"name": "Address And Contacts",
 					"label": "Supplier Addresses And Contacts",
 					"doctype": "Address",
 					"route_options": {
 						"party_type": "Supplier"
 					}
-				},
+				}
 			]
 		},
 		{
diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py
index 496617a..029fdac 100644
--- a/erpnext/config/selling.py
+++ b/erpnext/config/selling.py
@@ -120,7 +120,7 @@
 				{
 					"type": "report",
 					"is_query_report": True,
-					"name": "Addresses And Contacts",
+					"name": "Address And Contacts",
 					"label": _("Sales Partner Addresses And Contacts"),
 					"doctype": "Address",
 					"route_options": {
@@ -230,7 +230,7 @@
 				{
 					"type": "report",
 					"is_query_report": True,
-					"name": "Addresses And Contacts",
+					"name": "Address And Contacts",
 					"label": _("Customer Addresses And Contacts"),
 					"doctype": "Address",
 					"route_options": {
diff --git a/erpnext/selling/report/address_and_contacts/__init__.py b/erpnext/selling/report/address_and_contacts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/selling/report/address_and_contacts/__init__.py
diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.js b/erpnext/selling/report/address_and_contacts/address_and_contacts.js
new file mode 100644
index 0000000..383f18b
--- /dev/null
+++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.js
@@ -0,0 +1,34 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Address And Contacts"] = {
+	"filters": [
+		{
+			"reqd": 1,
+			"fieldname":"party_type",
+			"label": __("Party Type"),
+			"fieldtype": "Link",
+			"options": "DocType",
+			"get_query": function() {
+				return {
+					"filters": {
+						"name": ["in","Customer,Supplier,Sales Partner"],
+					}
+				}
+			}
+		},
+		{
+			"fieldname":"party_name",
+			"label": __("Party Name"),
+			"fieldtype": "Dynamic Link",
+			"get_options": function() {
+				let party_type = frappe.query_report_filters_by_name.party_type.get_value();
+				if(!party_type) {
+					frappe.throw(__("Please select Party Type first"));
+				}
+				return party_type;
+			}
+		}
+	]
+}
diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.json b/erpnext/selling/report/address_and_contacts/address_and_contacts.json
new file mode 100644
index 0000000..da38bab
--- /dev/null
+++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.json
@@ -0,0 +1,33 @@
+{
+ "add_total_row": 0, 
+ "apply_user_permissions": 1, 
+ "creation": "2018-06-01 09:32:13.088771", 
+ "disabled": 0, 
+ "docstatus": 0, 
+ "doctype": "Report", 
+ "idx": 0, 
+ "is_standard": "Yes", 
+ "letter_head": "Test", 
+ "modified": "2018-06-01 09:39:39.604944", 
+ "modified_by": "Administrator", 
+ "module": "Selling", 
+ "name": "Address And Contacts", 
+ "owner": "Administrator", 
+ "ref_doctype": "Address", 
+ "report_name": "Address And Contacts", 
+ "report_type": "Script Report", 
+ "roles": [
+  {
+   "role": "Sales User"
+  }, 
+  {
+   "role": "Purchase User"
+  }, 
+  {
+   "role": "Maintenance User"
+  }, 
+  {
+   "role": "Accounts User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/selling/report/address_and_contacts/address_and_contacts.py b/erpnext/selling/report/address_and_contacts/address_and_contacts.py
new file mode 100644
index 0000000..0a46d2c
--- /dev/null
+++ b/erpnext/selling/report/address_and_contacts/address_and_contacts.py
@@ -0,0 +1,120 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+from six.moves import range
+from six import iteritems
+import frappe
+
+
+field_map = {
+	"Contact": [ "first_name", "last_name", "phone", "mobile_no", "email_id", "is_primary_contact" ],
+	"Address": [ "address_line1", "address_line2", "city", "state", "pincode", "country", "is_primary_address" ]
+}
+
+def execute(filters=None):
+	columns, data = get_columns(filters), get_data(filters)
+	return columns, data
+
+def get_columns(filters):
+	party_type = filters.get("party_type")
+	party_type_value = get_party_group(party_type)
+	return [
+		"{party_type}:Link/{party_type}".format(party_type=party_type),
+		"{party_value_type}::150".format(party_value_type = frappe.unscrub(str(party_type_value))),
+		"Address Line 1",
+		"Address Line 2",
+		"City",
+		"State",
+		"Postal Code",
+		"Country",
+		"Is Primary Address:Check",
+		"First Name",
+		"Last Name",
+		"Phone",
+		"Mobile No",
+		"Email Id",
+		"Is Primary Contact:Check"
+	]
+
+def get_data(filters):
+	party_type = filters.get("party_type")
+	party = filters.get("party_name")
+	party_group = get_party_group(party_type)
+
+	return get_party_addresses_and_contact(party_type, party, party_group)
+
+def get_party_addresses_and_contact(party_type, party, party_group):
+	data = []
+	filters = None
+	party_details = frappe._dict()
+
+	if not party_type:
+		return []
+
+	if party:
+		filters = { "name": party }
+
+	fetch_party_list = frappe.get_list(party_type, filters=filters, fields=["name", party_group], as_list=True)
+	party_list = [d[0] for d in fetch_party_list]
+	party_groups = {}
+	for d in fetch_party_list:
+		party_groups[d[0]] = d[1]
+
+	for d in party_list:
+		party_details.setdefault(d, frappe._dict())
+
+	party_details = get_party_details(party_type, party_list, "Address", party_details)
+	party_details = get_party_details(party_type, party_list, "Contact", party_details)
+
+	for party, details in iteritems(party_details):
+		addresses = details.get("address", [])
+		contacts  = details.get("contact", [])
+		if not any([addresses, contacts]):
+			result = [party]
+			result.append(party_groups[party])
+			result.extend(add_blank_columns_for("Contact"))
+			result.extend(add_blank_columns_for("Address"))
+			data.append(result)
+		else:
+			addresses = map(list, addresses)
+			contacts = map(list, contacts)
+
+			max_length = max(len(addresses), len(contacts))
+			for idx in range(0, max_length):
+				result = [party]
+				result.append(party_groups[party])
+				address = addresses[idx] if idx < len(addresses) else add_blank_columns_for("Address")
+				contact = contacts[idx] if idx < len(contacts) else add_blank_columns_for("Contact")
+				result.extend(address)
+				result.extend(contact)
+
+				data.append(result)
+	return data
+
+def get_party_details(party_type, party_list, doctype, party_details):
+	filters =  [
+		["Dynamic Link", "link_doctype", "=", party_type],
+		["Dynamic Link", "link_name", "in", party_list]
+	]
+	fields = ["`tabDynamic Link`.link_name"] + field_map.get(doctype, [])
+
+	records = frappe.get_list(doctype, filters=filters, fields=fields, as_list=True)
+	for d in records:
+		details = party_details.get(d[0])
+		details.setdefault(frappe.scrub(doctype), []).append(d[1:])
+
+	return party_details
+
+def add_blank_columns_for(doctype):
+	return ["" for field in field_map.get(doctype, [])]
+
+def get_party_group(party_type):
+	if not party_type: return
+	group = {
+		"Customer": "customer_group",
+		"Supplier": "supplier_group",
+		"Sales Partner": "partner_type"
+	}
+
+	return group[party_type]
\ No newline at end of file