* [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): | |||
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 | |||
@@ -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): | |||
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 | |||
}) |
@@ -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", | |||
"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 = { | |||
@@ -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", | |||
@@ -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"); | |||
} | |||
); | |||
} | |||
} | |||
}) |