* [WIP] Address & Contacts fixes * [minor] included sales partner in Addresses and Contacts report * [minor] filters in address and contact query * [minor] address and contact utilsversion-14
@@ -114,6 +114,16 @@ def update_contact(doc, method): | |||||
def contact_query(doctype, txt, searchfield, start, page_len, filters): | def contact_query(doctype, txt, searchfield, start, page_len, filters): | ||||
from frappe.desk.reportview import get_match_cond | 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 | return frappe.db.sql("""select | ||||
contact.name, contact.first_name, contact.last_name | contact.name, contact.first_name, contact.last_name | ||||
from | from | ||||
@@ -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() |
@@ -50,10 +50,12 @@ class Address(Document): | |||||
def validate_reference(self): | def validate_reference(self): | ||||
if self.is_your_company_address: | 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")) | 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): | def get_display(self): | ||||
return get_address_display(self.as_dict()) | return get_address_display(self.as_dict()) | ||||
@@ -169,18 +171,32 @@ def get_address_templates(address): | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def get_shipping_address(company): | 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) | name, address_template = get_address_templates(address_as_dict) | ||||
return address_as_dict.get("name"), frappe.render_template(address_template, 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): | def address_query(doctype, txt, searchfield, start, page_len, filters): | ||||
from frappe.desk.reportview import get_match_cond | 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 | return frappe.db.sql("""select | ||||
address.name, address.city, address.country | address.name, address.city, address.country | ||||
from | from | ||||
@@ -191,18 +207,19 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): | |||||
dl.link_doctype = %(link_doctype)s and | dl.link_doctype = %(link_doctype)s and | ||||
dl.link_name = %(link_name)s and | dl.link_name = %(link_name)s and | ||||
address.`{key}` like %(txt)s | address.`{key}` like %(txt)s | ||||
{mcond} | |||||
{mcond} {condition} | |||||
order by | order by | ||||
if(locate(%(_txt)s, address.name), locate(%(_txt)s, address.name), 99999), | if(locate(%(_txt)s, address.name), locate(%(_txt)s, address.name), 99999), | ||||
address.idx desc, address.name | address.idx desc, address.name | ||||
limit %(start)s, %(page_len)s """.format( | limit %(start)s, %(page_len)s """.format( | ||||
mcond=get_match_cond(doctype), | 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': "%%%s%%" % frappe.db.escape(txt), | ||||
'_txt': txt.replace("%", ""), | '_txt': txt.replace("%", ""), | ||||
'start': start, | 'start': start, | ||||
'page_len': page_len, | 'page_len': page_len, | ||||
'link_doctype': filters.get('link_doctype'), | |||||
'link_name': filters.get('link_name') | |||||
'link_doctype': link_doctype, | |||||
'link_name': link_name | |||||
}) | }) |
@@ -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; | |||||
} | |||||
} | |||||
] | |||||
} |
@@ -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" | |||||
} |
@@ -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] | |||||
] |
@@ -81,6 +81,8 @@ permission_query_conditions = { | |||||
"ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions", | "ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions", | ||||
"User": "frappe.core.doctype.user.user.get_permission_query_conditions", | "User": "frappe.core.doctype.user.user.get_permission_query_conditions", | ||||
"Note": "frappe.desk.doctype.note.note.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 = { | has_permission = { | ||||
@@ -88,7 +90,9 @@ has_permission = { | |||||
"ToDo": "frappe.desk.doctype.todo.todo.has_permission", | "ToDo": "frappe.desk.doctype.todo.todo.has_permission", | ||||
"User": "frappe.core.doctype.user.user.has_permission", | "User": "frappe.core.doctype.user.user.has_permission", | ||||
"Note": "frappe.desk.doctype.note.note.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 = { | has_website_permission = { | ||||
@@ -125,6 +125,7 @@ | |||||
"public/js/frappe/misc/number_format.js", | "public/js/frappe/misc/number_format.js", | ||||
"public/js/frappe/misc/help.js", | "public/js/frappe/misc/help.js", | ||||
"public/js/frappe/misc/help_links.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/ui/upload.html", | ||||
"public/js/frappe/upload.js", | "public/js/frappe/upload.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"); | |||||
} | |||||
); | |||||
} | |||||
} | |||||
}) |