diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 6523b0b1e5..64d94dab4e 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -10,7 +10,7 @@ import re def load_address_and_contact(doc, key=None): """Loads address list and contact list in `__onload`""" - from frappe.contacts.doctype.address.address import get_address_display + from frappe.contacts.doctype.address.address import get_address_display, get_condensed_address filters = [ ["Dynamic Link", "link_doctype", "=", doc.doctype], @@ -37,6 +37,23 @@ def load_address_and_contact(doc, key=None): ] contact_list = frappe.get_all("Contact", filters=filters, fields=["*"]) + for contact in contact_list: + contact["email_ids"] = frappe.get_list("Contact Email", filters={ + "parenttype": "Contact", + "parent": contact.name, + "is_primary": 0 + }, fields=["email_id"]) + + contact["phone_nos"] = frappe.get_list("Contact Phone", filters={ + "parenttype": "Contact", + "parent": contact.name, + "is_primary": 0 + }, fields=["phone"]) + + if contact.address: + address = frappe.get_doc("Address", contact.address) + contact["address"] = get_condensed_address(address) + contact_list = sorted(contact_list, key = functools.cmp_to_key(lambda a, b: (int(a.is_primary_contact - b.is_primary_contact)) or diff --git a/frappe/contacts/doctype/address/address.json b/frappe/contacts/doctype/address/address.json index 2947af03ec..333bbe916a 100644 --- a/frappe/contacts/doctype/address/address.json +++ b/frappe/contacts/doctype/address/address.json @@ -1,666 +1,159 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, - "beta": 0, "creation": "2013-01-10 16:34:32", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "field_order": [ + "address_details", + "address_title", + "address_type", + "address_line1", + "address_line2", + "city", + "county", + "state", + "country", + "pincode", + "column_break0", + "email_id", + "phone", + "fax", + "is_primary_address", + "is_shipping_address", + "disabled", + "linked_with", + "is_your_company_address", + "links" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "address_details", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "options": "fa fa-map-marker", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-map-marker" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "address_title", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "address_type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Address Type", - "length": 0, - "no_copy": 0, "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "address_line1", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address Line 1", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "address_line2", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address Line 2", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Address Line 2" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "city", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, - "in_standard_filter": 0, "label": "City/Town", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "county", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "County", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "County" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "state", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "State", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "State" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "country", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Country", - "length": 0, - "no_copy": 0, "options": "Country", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "pincode", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Postal Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break0", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "email_id", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Email Address", - "length": 0, - "no_copy": 0, - "options": "Email", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Email" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "phone", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Phone", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Phone" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "fax", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Fax", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Fax" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "description": "", "fieldname": "is_primary_address", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Preferred Billing Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Preferred Billing Address" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "description": "", "fieldname": "is_shipping_address", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Preferred Shipping Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Preferred Shipping Address" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Disabled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "linked_with", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference", - "length": 0, - "no_copy": 0, - "options": "fa fa-pushpin", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "fa fa-pushpin" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", "fieldname": "is_your_company_address", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Your Company Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Is Your Company Address" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "links", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Links", - "length": 0, - "no_copy": 0, - "options": "Dynamic Link", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Dynamic Link" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-map-marker", "idx": 5, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-11-27 17:57:22.913973", + "modified": "2019-08-09 14:16:53.343251", "modified_by": "Administrator", "module": "Contacts", "name": "Address", @@ -668,109 +161,61 @@ "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Purchase User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Maintenance User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Accounts User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", "set_user_permissions": 1, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "country, state", - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} + "sort_order": "DESC" +} \ No newline at end of file diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 80b1c1fbe4..6795011745 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -143,13 +143,13 @@ def get_list_context(context=None): } def get_address_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by = None): - from frappe.www.list import get_list - user = frappe.session.user - ignore_permissions = False - if is_website_user(): - if not filters: filters = [] - add_name = [] - contact = frappe.db.sql(""" + from frappe.www.list import get_list + user = frappe.session.user + ignore_permissions = False + if is_website_user(): + if not filters: filters = [] + add_name = [] + contact = frappe.db.sql(""" select address.name from @@ -165,12 +165,12 @@ def get_address_list(doctype, txt, filters, limit_start, limit_page_length = 20, `tabDynamic Link` as link on contact.name = link.parent where contact.user = %s)""",(user)) - for c in contact: - add_name.append(c[0]) - filters.append(("Address", "name", "in", add_name)) - ignore_permissions = True + for c in contact: + add_name.append(c[0]) + filters.append(("Address", "name", "in", add_name)) + ignore_permissions = True - return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) + return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) def has_website_permission(doc, ptype, user, verbose=False): """Returns true if there is a related lead or contact related to this document""" @@ -277,3 +277,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): 'link_name': link_name, 'link_doctype': link_doctype }) + +def get_condensed_address(doc): + fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"] + return ", ".join([doc.get(d) for d in fields if doc.get(d)]) \ No newline at end of file diff --git a/frappe/contacts/doctype/contact/contact.json b/frappe/contacts/doctype/contact/contact.json index 69a8cf7fcd..f700411e80 100644 --- a/frappe/contacts/doctype/contact/contact.json +++ b/frappe/contacts/doctype/contact/contact.json @@ -13,14 +13,17 @@ "last_name", "email_id", "user", + "address", "cb00", "status", "salutation", "designation", "gender", "phone", - "mobile_no", "image", + "sb_00", + "email_ids", + "phone_nos", "contact_details", "is_primary_contact", "links", @@ -59,10 +62,12 @@ "fieldname": "email_id", "fieldtype": "Data", "in_global_search": 1, + "in_list_view": 1, "label": "Email Address", "oldfieldname": "email_id", "oldfieldtype": "Data", "options": "Email", + "read_only": 1, "search_index": 1 }, { @@ -101,18 +106,11 @@ "bold": 1, "fieldname": "phone", "fieldtype": "Data", + "in_list_view": 1, "label": "Phone", "oldfieldname": "contact_no", - "oldfieldtype": "Data" - }, - { - "bold": 1, - "fieldname": "mobile_no", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Mobile No", - "oldfieldname": "mobile_no", - "oldfieldtype": "Data" + "oldfieldtype": "Data", + "read_only": 1 }, { "fieldname": "image", @@ -182,12 +180,35 @@ "fieldname": "google_contacts_description", "fieldtype": "Small Text", "label": "Google Contacts Description" + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Contact Details" + }, + { + "fieldname": "email_ids", + "fieldtype": "Table", + "label": "Email IDs", + "options": "Contact Email" + }, + { + "fieldname": "address", + "fieldtype": "Link", + "label": "Address", + "options": "Address" + }, + { + "fieldname": "phone_nos", + "fieldtype": "Table", + "label": "Phone Nos", + "options": "Contact Phone" } ], "icon": "fa fa-user", "idx": 1, "image_field": "image", - "modified": "2019-06-14 20:35:45.300869", + "modified": "2019-08-09 10:23:00.486673", "modified_by": "Administrator", "module": "Contacts", "name": "Contact", diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 2018f5469e..085452988a 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -29,10 +29,16 @@ class Contact(Document): break def validate(self): + self.set_primary("email_id", "email_ids") + self.set_primary("phone", "phone_nos") + if self.email_id: self.email_id = self.email_id.strip() + self.set_user() + set_link_title(self) + if self.email_id and not self.image: self.image = has_gravatar(self.email_id) @@ -61,6 +67,33 @@ class Contact(Document): if (link.link_doctype, link.link_name) in reference_links: return True + def add_email(self, email_id, is_primary=0, autosave=False): + self.append("email_ids", { + "email_id": email_id, + "is_primary": is_primary + }) + + if autosave: + self.save(ignore_permissions=True) + + def add_phone(self, phone, is_primary=0, autosave=False): + self.append("phone_nos", { + "phone": phone, + "is_primary": is_primary + }) + + if autosave: + self.save(ignore_permissions=True) + + def set_primary(self, fieldname, child_table): + if len(self.get(child_table)) == 1: + self.get(child_table)[0].is_primary = 1 + setattr(self, fieldname, self.get(child_table)[0].get(fieldname)) + else: + for d in self.get(child_table): + if d.is_primary == 1: + setattr(self, fieldname, d.get(fieldname)) + break def get_default_contact(doctype, name): '''Returns default contact for the given doctype, name''' @@ -166,11 +199,12 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters): def get_contact_with_phone_number(number): if not number: return - contacts = frappe.get_all('Contact', or_filters={ - 'phone': ['like', '%{}'.format(number)], - 'mobile_no': ['like', '%{}'.format(number)] - }, limit=1) + contacts = frappe.get_all('Contact Phone', filters=[ + ['phone', 'like', '%{0}'.format(number)] + ], fields=["parent"], limit=1) - contact = contacts[0].name if contacts else None + return contacts[0].parent if contacts else None - return contact +def get_contact_name(email_id): + contact = frappe.get_list("Contact Email", filters={"email_id": email_id}, fields=["parent"], limit=1) + return contact[0].parent if contact else None diff --git a/frappe/contacts/doctype/contact_email/__init__.py b/frappe/contacts/doctype/contact_email/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/contacts/doctype/contact_email/contact_email.json b/frappe/contacts/doctype/contact_email/contact_email.json new file mode 100644 index 0000000000..cfcc39ca8b --- /dev/null +++ b/frappe/contacts/doctype/contact_email/contact_email.json @@ -0,0 +1,37 @@ +{ + "creation": "2019-08-02 13:08:59.291097", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "email_id", + "is_primary" + ], + "fields": [ + { + "fieldname": "email_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Email ID", + "options": "Email" + }, + { + "default": "0", + "fieldname": "is_primary", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Primary" + } + ], + "istable": 1, + "modified": "2019-08-02 13:14:22.193463", + "modified_by": "Administrator", + "module": "Contacts", + "name": "Contact Email", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/contacts/doctype/contact_email/contact_email.py b/frappe/contacts/doctype/contact_email/contact_email.py new file mode 100644 index 0000000000..04e8b22989 --- /dev/null +++ b/frappe/contacts/doctype/contact_email/contact_email.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ContactEmail(Document): + pass diff --git a/frappe/contacts/doctype/contact_phone/__init__.py b/frappe/contacts/doctype/contact_phone/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/contacts/doctype/contact_phone/contact_phone.json b/frappe/contacts/doctype/contact_phone/contact_phone.json new file mode 100644 index 0000000000..971dedf3d2 --- /dev/null +++ b/frappe/contacts/doctype/contact_phone/contact_phone.json @@ -0,0 +1,36 @@ +{ + "creation": "2019-08-02 13:10:37.890214", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "phone", + "is_primary" + ], + "fields": [ + { + "default": "0", + "fieldname": "is_primary", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Primary" + }, + { + "fieldname": "phone", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Phone" + } + ], + "istable": 1, + "modified": "2019-08-05 11:40:59.104224", + "modified_by": "Administrator", + "module": "Contacts", + "name": "Contact Phone", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/contacts/doctype/contact_phone/contact_phone.py b/frappe/contacts/doctype/contact_phone/contact_phone.py new file mode 100644 index 0000000000..fe2f86a4bd --- /dev/null +++ b/frappe/contacts/doctype/contact_phone/contact_phone.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ContactPhone(Document): + pass diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js index 0f8c2361ae..10137e80d5 100644 --- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js +++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js @@ -9,6 +9,13 @@ frappe.query_reports["Addresses And Contacts"] = { "label": __("Entity Type"), "fieldtype": "Link", "options": "DocType", + "get_query": function() { + return { + "filters": { + "name": ["in", "Contact, Address"], + } + } + } }, { "fieldname":"reference_name", diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py index f73858c8ab..fb345a4384 100644 --- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py @@ -7,8 +7,8 @@ import frappe from frappe import _ 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" ] + "Contact": ["first_name", "last_name", "address", "phone", "email_id", "is_primary_contact"], + "Address": ["address_line1", "address_line2", "city", "state", "pincode", "country", "is_primary_address"] } def execute(filters=None): @@ -27,8 +27,8 @@ def get_columns(filters): "Is Primary Address:Check", "First Name", "Last Name", + "Address", "Phone", - "Mobile No", "Email Id", "Is Primary Contact:Check" ] @@ -49,7 +49,7 @@ def get_reference_addresses_and_contact(reference_doctype, reference_name): return [] if reference_name: - filters = { "name": reference_name } + filters = {"name": reference_name} reference_list = [d[0] for d in frappe.get_list(reference_doctype, filters=filters, fields=["name"], as_list=True)] diff --git a/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py index e315019ca7..d87676e272 100644 --- a/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py @@ -67,20 +67,23 @@ def create_linked_address(link_list): address.insert() frappe.flags.test_address_created = True -def create_linked_contact(link_list): + return address.name + +def create_linked_contact(link_list, address): if frappe.flags.test_contact_created: return contact = frappe.get_doc({ "doctype": "Contact", "salutation": "Mr", - "email_id": "test_contact@example.com", "first_name": "_Test First Name", "last_name": "_Test Last Name", "is_primary_contact": 1, - "phone": "+91 0000000000", + "address": address, "status": "Open" }) + contact.add_email("test_contact@example.com") + contact.add_phone("+91 0000000000") for name in link_list: contact.append("links",{ @@ -88,7 +91,7 @@ def create_linked_contact(link_list): 'link_name': name }) - contact.insert() + contact.insert(ignore_permissions=True) frappe.flags.test_contact_created = True @@ -96,11 +99,11 @@ class TestAddressesAndContacts(unittest.TestCase): def test_get_data(self): linked_docs = [get_custom_doc_for_address_and_contacts()] links_list = [item.name for item in linked_docs] - create_linked_contact(links_list) - create_linked_address(links_list) + d = create_linked_address(links_list) + create_linked_contact(links_list, d) report_data = get_data({"reference_doctype": "Test Custom Doctype"}) for idx, link in enumerate(links_list): - test_item = [link, 'test address line 1', 'test address line 2', 'Milan', None, None, 'Italy', 0, '_Test First Name', '_Test Last Name', '+91 0000000000', None, 'test_contact@example.com', 1] + test_item = [link, 'test address line 1', 'test address line 2', 'Milan', None, None, 'Italy', 0, '_Test First Name', '_Test Last Name', '_Test Address-Billing', '+91 0000000000', 'test_contact@example.com', 1] self.assertListEqual(test_item, report_data[idx]) def tearDown(self): diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index c773f23cae..c32dfcee99 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -15,6 +15,7 @@ from frappe.core.doctype.comment.comment import update_comment_in_doc from email.utils import parseaddr from six.moves.urllib.parse import unquote from collections import Counter +from frappe.contacts.doctype.contact.contact import get_contact_name exclude_from_linked_with = True @@ -334,14 +335,15 @@ def get_contacts(email_strings): contacts = [] for email in email_addrs: email = get_email_without_link(email) - contact_name = frappe.db.get_value('Contact', {'email_id': email}) + contact_name = get_contact_name(email) if not contact_name: contact = frappe.get_doc({ - "doctype": "Contact", - "first_name": frappe.unscrub(email.split("@")[0]), - "email_id": email - }).insert(ignore_permissions=True) + "doctype": "Contact", + "first_name": frappe.unscrub(email.split("@")[0]), + }) + contact.add_email(email) + contact.insert(ignore_permissions=True) contact_name = contact.name contacts.append(contact_name) diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py index 3ce5646c7e..115b711a94 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -100,21 +100,24 @@ class TestCommunication(unittest.TestCase): def test_contacts_attached(self): contact_sender = frappe.get_doc({ "doctype": "Contact", - "first_name": frappe.generate_hash(length=10), - "email_id": "comm_sender@example.com" - }).insert(ignore_permissions=True) + "first_name": "contact_sender", + }) + contact_sender.add_email("comm_sender@example.com") + contact_sender.insert(ignore_permissions=True) contact_recipient = frappe.get_doc({ "doctype": "Contact", - "first_name": frappe.generate_hash(length=10), - "email_id": "comm_recipient@example.com" - }).insert(ignore_permissions=True) + "first_name": "contact_recipient", + }) + contact_recipient.add_email("comm_recipient@example.com") + contact_recipient.insert(ignore_permissions=True) contact_cc = frappe.get_doc({ "doctype": "Contact", - "first_name": frappe.generate_hash(length=10), - "email_id": "comm_cc@example.com" - }).insert(ignore_permissions=True) + "first_name": "contact_cc", + }) + contact_cc.add_email("comm_cc@example.com") + contact_cc.insert(ignore_permissions=True) comm = frappe.get_doc({ "doctype": "Communication", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 73c4dab3d1..cd754aef3a 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1038,16 +1038,23 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False): if user.name in ["Administrator", "Guest"]: return if not frappe.db.get_value("Contact", {"email_id": user.email}): - frappe.get_doc({ + contact = frappe.get_doc({ "doctype": "Contact", "first_name": user.first_name, "last_name": user.last_name, - "email_id": user.email, "user": user.name, "gender": user.gender, - "phone": user.phone, - "mobile_no": user.mobile_no - }).insert(ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory) + }) + + if user.email: + contact.add_email(user.email) + + if user.phone: + contact.add_phone(user.phone) + + if user.mobile_no: + contact.add_phone(user.mobile_no) + contact.insert(ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory) @frappe.whitelist() diff --git a/frappe/hooks.py b/frappe/hooks.py index 83ebabe6e0..eb0d5bbe77 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -261,7 +261,11 @@ user_privacy_documents = [ { 'doctype': 'Contact', 'match_field': 'email_id', - 'personal_fields': ['first_name', 'last_name', 'phone', 'mobile_no'], + 'personal_fields': ['first_name', 'last_name', 'phone'], + }, + { + 'doctype': 'Contact Email', + 'match_field': 'email_id', }, { 'doctype': 'Address', diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index e7eaf928d1..4955fc9d7f 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -136,24 +136,25 @@ def sync(g_contact=None): for name in connection.get("names"): if name.get("metadata").get("primary"): - if connection.get("emailAddresses"): - for email in connection.get("emailAddresses"): - if not frappe.db.exists("Contact", {"email_id": email.get("value")}): - contacts_updated += 1 + contact = frappe.get_doc({ + "doctype": "Contact", + "salutation": name.get("honorificPrefix") or "", + "first_name": name.get("givenName") or "", + "middle_name": name.get("middleName") or "", + "last_name": name.get("familyName") or "", + "designation": get_indexed_value(connection.get("organizations"), 0, "title"), + "source": "Google Contacts", + "google_contacts_description": get_indexed_value(connection.get("organizations"), 0, "name") + }) + + for email in connection.get("emailAddresses", []): + contact.add_email(email_id=email.get("value"), is_primary=1 if email.get("primary") else 0) + + for phone in connection.get("phoneNumbers", []): + contact.add_phone(phone=phone.get("value"), is_primary=1 if phone.get("primary") else 0) + + contact.insert(ignore_permissions=True) - frappe.get_doc({ - "doctype": "Contact", - "salutation": name.get("honorificPrefix") if name.get("honorificPrefix") else "", - "first_name": name.get("givenName") if name.get("givenName") else "", - "middle_name": name.get("middleName") if name.get("middleName") else "", - "last_name": name.get("familyName") if name.get("familyName") else "", - "email_id": email.get("value") if email.get("value") else "", - "designation": get_indexed_value(connection.get("organizations"), 0, "title"), - "phone": get_indexed_value(connection.get("phoneNumbers"), 0, "value"), - "mobile_no": get_indexed_value(connection.get("phoneNumbers"), 1, "value"), - "source": "Google Contacts", - "google_contacts_description": get_indexed_value(connection.get("organizations"), 0, "name") - }).insert(ignore_permissions=True) if g_contact: return _("{0} Google Contacts synced.").format(contacts_updated) if contacts_updated > 0 else _("No new Google Contacts synced.") diff --git a/frappe/patches.txt b/frappe/patches.txt index c90fc89a86..5cdc089826 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -248,4 +248,5 @@ frappe.patches.v12_0.move_timeline_links_to_dynamic_links frappe.patches.v12_0.delete_feedback_request_if_exists #1 frappe.patches.v12_0.rename_events_repeat_on frappe.patches.v12_0.fix_public_private_files +frappe.patches.v12_0.move_email_and_phone_to_child_table frappe.patches.v12_0.delete_duplicate_indexes diff --git a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py new file mode 100644 index 0000000000..bb23fedfd5 --- /dev/null +++ b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py @@ -0,0 +1,31 @@ +import frappe + +def execute(): + contact_details = frappe.get_list("Contact", fields=["name", "email_id", "phone", "mobile_no", "modified_by", "creation", "modified"]) + frappe.reload_doc("contacts", "doctype", "contact_email") + frappe.reload_doc("contacts", "doctype", "contact_phone") + frappe.reload_doc("contacts", "doctype", "contact") + + for contact_detail in contact_details: + contact_name = frappe.db.escape(contact_detail.name) + + if contact_detail.email_id: + frappe.db.sql(""" + INSERT INTO `tabContact Email` + (`idx`, `name`, `email_id`, `parentfield`, `parenttype`, `parent`, `is_primary`, `creation`, `modified`, `modified_by`) + VALUES (1, '{0}', '{1}', 'email_ids', 'Contact', {2}, 1, '{3}', '{4}', '{5}') + """.format(frappe.generate_hash(contact_detail.email_id, 10), contact_detail.email_id, contact_name, contact_detail.creation, contact_detail.modified, contact_detail.modified_by)) + + if contact_detail.phone: + frappe.db.sql(""" + INSERT INTO `tabContact Phone` + (`idx`, `name`, `phone`, `parentfield`, `parenttype`, `parent`, `is_primary`, `creation`, `modified`, `modified_by`) + VALUES (1, '{0}', '{1}', 'phone_nos', 'Contact', {2}, 1, '{3}', '{4}', '{5}') + """.format(frappe.generate_hash(contact_detail.phone, 10), contact_detail.phone, contact_name, contact_detail.creation, contact_detail.modified, contact_detail.modified_by)) + + if contact_detail.mobile_no: + frappe.db.sql(""" + INSERT INTO `tabContact Phone` + (`idx`, `name`, `phone`, `parentfield`, `parenttype`, `parent`, `is_primary`, `creation`, `modified`, `modified_by`) + VALUES (2, '{0}', '{1}', 'phone_nos', 'Contact', {2}, 0, '{3}', '{4}', '{5}') + """.format(frappe.generate_hash(contact_detail.mobile_no, 10), contact_detail.mobile_no, contact_name, contact_detail.creation, contact_detail.modified, contact_detail.modified_by)) \ No newline at end of file