diff --git a/frappe/email/doctype/contact/contact.py b/frappe/email/doctype/contact/contact.py index e43a997f28..e1275d3f96 100644 --- a/frappe/email/doctype/contact/contact.py +++ b/frappe/email/doctype/contact/contact.py @@ -114,6 +114,16 @@ def update_contact(doc, method): def contact_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond + link_doctype = filters.pop('link_doctype') + link_name = filters.pop('link_name') + + condition = "" + for fieldname, value in filters.iteritems(): + condition += " and {field}={value}".format( + field=fieldname, + value=value + ) + return frappe.db.sql("""select contact.name, contact.first_name, contact.last_name from diff --git a/frappe/geo/address_and_contact.py b/frappe/geo/address_and_contact.py new file mode 100644 index 0000000000..a1d802b4a4 --- /dev/null +++ b/frappe/geo/address_and_contact.py @@ -0,0 +1,140 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def load_address_and_contact(doc, key): + """Loads address list and contact list in `__onload`""" + from frappe.geo.doctype.address.address import get_address_display + + filters = [ + ["Dynamic Link", "link_doctype", "=", doc.doctype], + ["Dynamic Link", "link_name", "=", doc.name], + ["Dynamic Link", "parenttype", "=", "Address"], + ] + address_list = frappe.get_all("Address", filters=filters, fields=["*"]) + + address_list = [a.update({"display": get_address_display(a)}) + for a in address_list] + + address_list = sorted(address_list, + lambda a, b: + (int(a.is_primary_address - b.is_primary_address)) or + (1 if a.modified - b.modified else 0)) + + doc.set_onload('addr_list', address_list) + + if doc.doctype != "Lead": + filters = [ + ["Dynamic Link", "link_doctype", "=", doc.doctype], + ["Dynamic Link", "link_name", "=", doc.name], + ["Dynamic Link", "parenttype", "=", "Contact"], + ] + contact_list = frappe.get_all("Contact", filters=filters, fields=["*"]) + + contact_list = sorted(contact_list, + lambda a, b: + (int(a.is_primary_contact - b.is_primary_contact)) or + (1 if a.modified - b.modified else 0)) + + doc.set_onload('contact_list', contact_list) + +def set_default_role(doc, method): + '''Set customer, supplier, student based on email''' + if frappe.flags.setting_role: + return + contact_name = frappe.get_value('Contact', dict(email_id=doc.email)) + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + for link in contact.links: + frappe.flags.setting_role = True + if link.link_doctype=='Customer': + doc.add_roles('Customer') + elif link.link_doctype=='Supplier': + doc.add_roles('Supplier') + elif frappe.get_value('Student', dict(student_email_id=doc.email)): + doc.add_roles('Student') + +def has_permission(doc, ptype, user): + links = get_permitted_and_not_permitted_links(doc.doctype) + if not links.get("not_permitted_links"): + # optimization: don't determine permissions based on link fields + return True + + # True if any one is True or all are empty + names = [] + for df in (links.get("permitted_links") + links.get("not_permitted_links")): + doctype = df.options + name = doc.get(df.fieldname) + names.append(name) + + if name and frappe.has_permission(doctype, ptype, doc=name): + return True + + if not any(names): + return True + return False + +def get_permission_query_conditions_for_contact(user): + return get_permission_query_conditions("Contact") + +def get_permission_query_conditions_for_address(user): + return get_permission_query_conditions("Address") + +def get_permission_query_conditions(doctype): + links = get_permitted_and_not_permitted_links(doctype) + + if not links.get("not_permitted_links"): + # when everything is permitted, don't add additional condition + return "" + + elif not links.get("permitted_links"): + conditions = [] + + # when everything is not permitted + for df in links.get("not_permitted_links"): + # like ifnull(customer, '')='' and ifnull(supplier, '')='' + conditions.append("ifnull(`tab{doctype}`.`{fieldname}`, '')=''".format(doctype=doctype, fieldname=df.fieldname)) + + return "( " + " and ".join(conditions) + " )" + + else: + conditions = [] + + for df in links.get("permitted_links"): + # like ifnull(customer, '')!='' or ifnull(supplier, '')!='' + conditions.append("ifnull(`tab{doctype}`.`{fieldname}`, '')!=''".format(doctype=doctype, fieldname=df.fieldname)) + + return "( " + " or ".join(conditions) + " )" + +def get_permitted_and_not_permitted_links(doctype): + permitted_links = [] + not_permitted_links = [] + + meta = frappe.get_meta(doctype) + + for df in meta.get_link_fields(): + if df.options not in ("Customer", "Supplier", "Company", "Sales Partner"): + continue + + if frappe.has_permission(df.options): + permitted_links.append(df) + else: + not_permitted_links.append(df) + + return { + "permitted_links": permitted_links, + "not_permitted_links": not_permitted_links + } + +def delete_contact_and_address(doctype, docname): + for parenttype in ('Contact', 'Address'): + items = frappe.db.sql_list("""select parent from `tabDynamic Link` + where parenttype=%s and link_doctype=%s and link_name=%s""", + (parenttype, doctype, docname)) + + for name in items: + doc = frappe.get_doc(parenttype, name) + if len(doc.links)==1: + doc.delete() \ No newline at end of file diff --git a/frappe/geo/doctype/address/address.py b/frappe/geo/doctype/address/address.py index 7f59199640..a161402ef6 100644 --- a/frappe/geo/doctype/address/address.py +++ b/frappe/geo/doctype/address/address.py @@ -50,10 +50,12 @@ class Address(Document): def validate_reference(self): if self.is_your_company_address: - if not self.company: + if not [row for row in self.links if row.link_doctype == "Company"]: frappe.throw(_("Company is mandatory, as it is your company address")) - if self.links: - self.links = [] + + # removing other links + to_remove = [row for row in self.links if row.link_doctype != "Company"] + [ self.remove(row) for row in to_remove ] def get_display(self): return get_address_display(self.as_dict()) @@ -169,18 +171,32 @@ def get_address_templates(address): @frappe.whitelist() def get_shipping_address(company): - filters = {"company": company, "is_your_company_address":1} - fieldname = ["name", "address_line1", "address_line2", "city", "state", "country"] - - address_as_dict = frappe.db.get_value("Address", filters=filters, fieldname=fieldname, as_dict=True) - - if address_as_dict: + filters = [ + ["Dynamic Link", "link_doctype", "=", "Company"], + ["Dynamic Link", "link_name", "=", company], + ["Address", "is_your_company_address", "=", 1] + ] + fields = ["name", "address_line1", "address_line2", "city", "state", "country"] + address = frappe.get_all("Address", filters=filters, fields=fields) or {} + + if address: + address_as_dict = address[0] name, address_template = get_address_templates(address_as_dict) return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict) def address_query(doctype, txt, searchfield, start, page_len, filters): from frappe.desk.reportview import get_match_cond + link_doctype = filters.pop('link_doctype') + link_name = filters.pop('link_name') + + condition = "" + for fieldname, value in filters.iteritems(): + condition += " and {field}={value}".format( + field=fieldname, + value=value + ) + return frappe.db.sql("""select address.name, address.city, address.country from @@ -191,18 +207,19 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): dl.link_doctype = %(link_doctype)s and dl.link_name = %(link_name)s and address.`{key}` like %(txt)s - {mcond} + {mcond} {condition} order by if(locate(%(_txt)s, address.name), locate(%(_txt)s, address.name), 99999), address.idx desc, address.name limit %(start)s, %(page_len)s """.format( mcond=get_match_cond(doctype), - key=frappe.db.escape(searchfield)), + key=frappe.db.escape(searchfield), + condition=condition or ""), { 'txt': "%%%s%%" % frappe.db.escape(txt), '_txt': txt.replace("%", ""), 'start': start, 'page_len': page_len, - 'link_doctype': filters.get('link_doctype'), - 'link_name': filters.get('link_name') + 'link_doctype': link_doctype, + 'link_name': link_name }) diff --git a/frappe/geo/report/__init__.py b/frappe/geo/report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/geo/report/addresses_and_contacts/__init__.py b/frappe/geo/report/addresses_and_contacts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.js b/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.js new file mode 100644 index 0000000000..32261a9454 --- /dev/null +++ b/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.js @@ -0,0 +1,34 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.query_reports["Addresses 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"], + } + } + }, + "default": "Customer" + }, + { + "fieldname":"party_name", + "label": __("Party Name"), + "fieldtype": "Dynamic Link", + "get_options": function() { + var 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/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.json b/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.json new file mode 100644 index 0000000000..f229d15e7e --- /dev/null +++ b/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.json @@ -0,0 +1,18 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2017-01-19 12:57:22.881566", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2017-01-19 12:57:39.643565", + "modified_by": "Administrator", + "module": "Geo", + "name": "Addresses And Contacts", + "owner": "Administrator", + "ref_doctype": "Address", + "report_name": "Addresses And Contacts", + "report_type": "Script Report" +} \ No newline at end of file diff --git a/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.py new file mode 100644 index 0000000000..819c73e290 --- /dev/null +++ b/frappe/geo/report/addresses_and_contacts/addresses_and_contacts.py @@ -0,0 +1,90 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +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): + return [ + "{party_type}:Link/{party_type}".format(party_type=filters.get("party_type")), + "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): + data = [] + party_type = filters.get("party_type") + party = filters.get("party_name") + + return get_party_addresses_and_contact(party_type, party) + +def get_party_addresses_and_contact(party_type, party): + data = [] + filters = None + party_details = [] + + if not party_type: + return [] + + if party: + filters = { "name": party } + + party_details = frappe.get_list(party_type, filters=filters, fields=["name"], as_list=True) + for party_detail in map(list, party_details): + docname = party_detail[0] + + addresses = get_party_details(party_type, docname, doctype="Address") + contacts = get_party_details(party_type, docname, doctype="Contact") + + if not any([addresses, contacts]): + party_detail.extend([ "" for field in field_map.get("Address", []) ]) + party_detail.extend([ "" for field in field_map.get("Contact", []) ]) + data.append(party_detail) + else: + addresses = map(list, addresses) + contacts = map(list, contacts) + + max_length = max(len(addresses), len(contacts)) + for idx in xrange(0, max_length): + result = list(party_detail) + + address = addresses[idx] if idx < len(addresses) else [ "" for field in field_map.get("Address", []) ] + contact = contacts[idx] if idx < len(contacts) else [ "" for field in field_map.get("Contact", []) ] + result.extend(address) + result.extend(contact) + + data.append(result) + return data + +def get_party_details(party_type, docname, doctype="Address", fields=None): + default_filters = get_default_address_contact_filters(party_type, docname) + if not fields: + fields = field_map.get(doctype, ["name"]) + return frappe.get_list(doctype, filters=default_filters, fields=fields, as_list=True) + +def get_default_address_contact_filters(party_type, docname): + return [ + ["Dynamic Link", "link_doctype", "=", party_type], + ["Dynamic Link", "link_name", "=", docname] + ] \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index b3585d8754..c0a1e48ec9 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -81,6 +81,8 @@ permission_query_conditions = { "ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions", "User": "frappe.core.doctype.user.user.get_permission_query_conditions", "Note": "frappe.desk.doctype.note.note.get_permission_query_conditions", + "Contact": "frappe.geo.address_and_contact.get_permission_query_conditions_for_contact", + "Address": "frappe.geo.address_and_contact.get_permission_query_conditions_for_address" } has_permission = { @@ -88,7 +90,9 @@ has_permission = { "ToDo": "frappe.desk.doctype.todo.todo.has_permission", "User": "frappe.core.doctype.user.user.has_permission", "Note": "frappe.desk.doctype.note.note.has_permission", - "Communication": "frappe.core.doctype.communication.communication.has_permission" + "Contact": "erpnext.utilities.address_and_contact.has_permission", + "Address": "erpnext.utilities.address_and_contact.has_permission", + "Communication": "frappe.core.doctype.communication.communication.has_permission", } has_website_permission = { diff --git a/frappe/public/build.json b/frappe/public/build.json index 0981b64068..b430c3ac5b 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -125,6 +125,7 @@ "public/js/frappe/misc/number_format.js", "public/js/frappe/misc/help.js", "public/js/frappe/misc/help_links.js", + "public/js/frappe/misc/address_and_contact.js", "public/js/frappe/ui/upload.html", "public/js/frappe/upload.js", diff --git a/frappe/public/js/frappe/misc/address_and_contact.js b/frappe/public/js/frappe/misc/address_and_contact.js new file mode 100644 index 0000000000..750e9df405 --- /dev/null +++ b/frappe/public/js/frappe/misc/address_and_contact.js @@ -0,0 +1,29 @@ +frappe.provide('frappe.geo') + +$.extend(frappe.geo, { + clear_address_and_contact: function(frm) { + $(frm.fields_dict['address_html'].wrapper).html(""); + frm.fields_dict['contact_html'] && $(frm.fields_dict['contact_html'].wrapper).html(""); + }, + + render_address_and_contact: function(frm) { + // render address + $(frm.fields_dict['address_html'].wrapper) + .html(frappe.render_template("address_list", + cur_frm.doc.__onload)) + .find(".btn-address").on("click", function() { + frappe.new_doc("Address"); + }); + + // render contact + if(frm.fields_dict['contact_html']) { + $(frm.fields_dict['contact_html'].wrapper) + .html(frappe.render_template("contact_list", + cur_frm.doc.__onload)) + .find(".btn-contact").on("click", function() { + frappe.new_doc("Contact"); + } + ); + } + } +}) \ No newline at end of file