From dceb0d1b85f3ea9bc983237de59ac96fc86b7ef8 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 20 Jun 2019 11:26:07 +0530 Subject: [PATCH] feat(Integration): Google Contacts Integration (#7625) --- frappe/config/integrations.py | 10 + frappe/contacts/doctype/contact/contact.json | 1148 ++++------------- frappe/hooks.py | 5 +- .../doctype/google_contacts/__init__.py | 0 .../google_contacts/google_contacts.js | 56 + .../google_contacts/google_contacts.json | 107 ++ .../google_contacts/google_contacts.py | 166 +++ .../doctype/google_settings/__init__.py | 0 .../google_settings/google_settings.js | 9 + .../google_settings/google_settings.json | 69 + .../google_settings/google_settings.py | 10 + 11 files changed, 703 insertions(+), 877 deletions(-) create mode 100644 frappe/integrations/doctype/google_contacts/__init__.py create mode 100644 frappe/integrations/doctype/google_contacts/google_contacts.js create mode 100644 frappe/integrations/doctype/google_contacts/google_contacts.json create mode 100644 frappe/integrations/doctype/google_contacts/google_contacts.py create mode 100644 frappe/integrations/doctype/google_settings/__init__.py create mode 100644 frappe/integrations/doctype/google_settings/google_settings.js create mode 100644 frappe/integrations/doctype/google_settings/google_settings.json create mode 100644 frappe/integrations/doctype/google_settings/google_settings.py diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py index cd065e53f2..ce71051c1d 100644 --- a/frappe/config/integrations.py +++ b/frappe/config/integrations.py @@ -87,6 +87,11 @@ def get_data(): { "label": _("Google Services"), "items": [ + { + "type": "doctype", + "name": "Google Settings", + "description": _("Google API Settings."), + }, { "type": "doctype", "name": "Google Maps Settings", @@ -111,6 +116,11 @@ def get_data(): "type": "doctype", "name": "GSuite Templates", "description": _("Google GSuite Templates to integration with DocTypes"), + }, + { + "type": "doctype", + "name": "Google Contacts", + "description": _("Google Contacts Integration."), } ] } diff --git a/frappe/contacts/doctype/contact/contact.json b/frappe/contacts/doctype/contact/contact.json index 39a5ea6ad2..69a8cf7fcd 100644 --- a/frappe/contacts/doctype/contact/contact.json +++ b/frappe/contacts/doctype/contact/contact.json @@ -1,924 +1,322 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 1, - "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, - "engine": "InnoDB", + "allow_events_in_timeline": 1, + "allow_import": 1, + "allow_rename": 1, + "creation": "2013-01-10 16:34:32", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "contact_section", + "first_name", + "middle_name", + "last_name", + "email_id", + "user", + "cb00", + "status", + "salutation", + "designation", + "gender", + "phone", + "mobile_no", + "image", + "contact_details", + "is_primary_contact", + "links", + "more_info", + "department", + "unsubscribed", + "column_break_17", + "source", + "google_contacts_description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_section", - "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-user", - "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 - }, + "fieldname": "contact_section", + "fieldtype": "Section Break", + "options": "fa fa-user" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "first_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "First Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "first_name", - "oldfieldtype": "Data", - "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 - }, + "fieldname": "first_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "First Name", + "oldfieldname": "first_name", + "oldfieldtype": "Data", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "last_name", - "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": "Last Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "last_name", - "oldfieldtype": "Data", - "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 - }, + "bold": 1, + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name", + "oldfieldname": "last_name", + "oldfieldtype": "Data" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "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": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email Address", - "length": 0, - "no_copy": 0, - "oldfieldname": "email_id", - "oldfieldtype": "Data", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "bold": 1, + "fieldname": "email_id", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Email Address", + "oldfieldname": "email_id", + "oldfieldtype": "Data", + "options": "Email", + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "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": 0, - "label": "User Id", - "length": 0, - "no_copy": 0, - "options": "User", - "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 - }, + "fieldname": "user", + "fieldtype": "Link", + "in_global_search": 1, + "label": "User Id", + "options": "User" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb00", - "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 - }, + "fieldname": "cb00", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Passive", - "fieldname": "status", - "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": "Status", - "length": 0, - "no_copy": 0, - "options": "Passive\nOpen\nReplied", - "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 - }, + "default": "Passive", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Passive\nOpen\nReplied" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salutation", - "fieldtype": "Link", - "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": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "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 - }, + "fieldname": "salutation", + "fieldtype": "Link", + "label": "Salutation", + "options": "Salutation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gender", - "fieldtype": "Link", - "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": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "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 - }, + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "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, - "oldfieldname": "contact_no", - "oldfieldtype": "Data", - "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 - }, + "bold": 1, + "fieldname": "phone", + "fieldtype": "Data", + "label": "Phone", + "oldfieldname": "contact_no", + "oldfieldtype": "Data" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "mobile_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile No", - "length": 0, - "no_copy": 0, - "oldfieldname": "mobile_no", - "oldfieldtype": "Data", - "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 - }, + "bold": 1, + "fieldname": "mobile_no", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Mobile No", + "oldfieldname": "mobile_no", + "oldfieldtype": "Data" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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 - }, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_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": "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 - }, + "fieldname": "contact_details", + "fieldtype": "Section Break", + "label": "Reference", + "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", - "depends_on": "", - "fieldname": "is_primary_contact", - "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 Primary Contact", - "length": 0, - "no_copy": 0, - "oldfieldname": "is_primary_contact", - "oldfieldtype": "Select", - "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 - }, + "default": "0", + "fieldname": "is_primary_contact", + "fieldtype": "Check", + "label": "Is Primary Contact", + "oldfieldname": "is_primary_contact", + "oldfieldtype": "Select" + }, { - "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 - }, + "fieldname": "links", + "fieldtype": "Table", + "label": "Links", + "options": "Dynamic Link" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "more_info", - "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": "More Information", - "length": 0, - "no_copy": 0, - "options": "fa fa-file-text", - "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 - }, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "options": "fa fa-file-text" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "department", - "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": "Department", - "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 - }, + "fieldname": "department", + "fieldtype": "Data", + "label": "Department" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "designation", - "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": "Designation", - "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 - }, + "fieldname": "designation", + "fieldtype": "Data", + "label": "Designation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_17", - "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, - "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 - }, + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "unsubscribed", - "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": "Unsubscribed", - "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 + "default": "0", + "fieldname": "unsubscribed", + "fieldtype": "Check", + "label": "Unsubscribed" + }, + { + "fieldname": "source", + "fieldtype": "Data", + "label": "Source" + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name" + }, + { + "depends_on": "eval:doc.source==\"Google Contacts\"", + "fieldname": "google_contacts_description", + "fieldtype": "Small Text", + "label": "Google Contacts Description" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-user", - "idx": 1, - "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-24 15:37:10.254020", - "modified_by": "Administrator", - "module": "Contacts", - "name": "Contact", - "name_case": "Title Case", - "owner": "Administrator", + ], + "icon": "fa fa-user", + "idx": 1, + "image_field": "image", + "modified": "2019-06-14 20:35:45.300869", + "modified_by": "Administrator", + "module": "Contacts", + "name": "Contact", + "name_case": "Title Case", + "owner": "Administrator", "permissions": [ { - "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, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 1, + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales Master Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Master Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Master Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Master Manager", + "share": 1, "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": "Sales Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "share": 1, "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 Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "share": 1, "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 Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance Manager", + "share": 1, "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 Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "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": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, "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, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "share": 1, "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, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "share": 1, "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, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "match": "", - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "permlevel": 1, + "read": 1, + "report": 1, + "role": "All" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index f2b2ee9c1e..3e3a6647f6 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -160,7 +160,7 @@ scheduler_events = { "frappe.limits.update_site_usage", "frappe.desk.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry", "frappe.deferred_insert.save_to_db", - "frappe.desk.form.document_follow.send_hourly_updates" + "frappe.desk.form.document_follow.send_hourly_updates", ], "daily": [ "frappe.email.queue.clear_outbox", @@ -176,7 +176,8 @@ scheduler_events = { "frappe.core.doctype.activity_log.activity_log.clear_authentication_logs", "frappe.website.doctype.personal_data_deletion_request.personal_data_deletion_request.remove_unverified_record", "frappe.desk.form.document_follow.send_daily_updates", - "frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points" + "frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points", + "frappe.integrations.doctype.google_contacts.google_contacts.sync", ], "daily_long": [ "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily", diff --git a/frappe/integrations/doctype/google_contacts/__init__.py b/frappe/integrations/doctype/google_contacts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.js b/frappe/integrations/doctype/google_contacts/google_contacts.js new file mode 100644 index 0000000000..acd5fb35ae --- /dev/null +++ b/frappe/integrations/doctype/google_contacts/google_contacts.js @@ -0,0 +1,56 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Google Contacts', { + refresh: function(frm) { + frm.set_value("user", frappe.session.user); + + frappe.realtime.on('import_google_contacts', (data) => { + if (data.progress) { + frm.dashboard.show_progress('Import Google Contacts', data.progress / data.total * 100, + __('Importing {0} of {1}', [data.progress, data.total])); + if (data.progress === data.total) { + frm.dashboard.hide_progress('Import Google Contacts'); + } + } + }); + + if (frm.doc.refresh_token) { + frm.add_custom_button(__('Sync Contacts'), function () { + frappe.show_alert({ + indicator: 'green', + message: __('Syncing') + }); + frappe.call({ + method: "frappe.integrations.doctype.google_contacts.google_contacts.sync", + args: { + "g_contact": frm.doc.name + }, + }).then((r) => { + frappe.hide_progress(); + frappe.msgprint(r.message); + }); + }); + } + }, + authorize_google_contacts_access: function(frm) { + let reauthorize = 0; + if(frm.doc.authorization_code) { + reauthorize = 1; + } + + frappe.call({ + method: "frappe.integrations.doctype.google_contacts.google_contacts.authorize_access", + args: { + "g_contact": frm.doc.name, + "reauthorize": reauthorize + }, + callback: function(r) { + if(!r.exc) { + frm.save(); + window.open(r.message.url); + } + } + }); + } +}); diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.json b/frappe/integrations/doctype/google_contacts/google_contacts.json new file mode 100644 index 0000000000..9bd14d722f --- /dev/null +++ b/frappe/integrations/doctype/google_contacts/google_contacts.json @@ -0,0 +1,107 @@ +{ + "autoname": "format:GC-{user}", + "creation": "2019-06-14 00:09:39.441961", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable", + "sb_00", + "email_id", + "authorize_google_contacts_access", + "cb_00", + "user", + "last_sync_on", + "authorization_code", + "refresh_token" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User", + "read_only": 1 + }, + { + "fieldname": "authorization_code", + "fieldtype": "Password", + "hidden": 1, + "label": "Authorization Code" + }, + { + "fieldname": "refresh_token", + "fieldtype": "Password", + "hidden": 1, + "label": "Refresh Token" + }, + { + "fieldname": "last_sync_on", + "fieldtype": "Datetime", + "label": "Last Sync On", + "read_only": 1 + }, + { + "description": "Email Address whose Google Contacts are to be synced.", + "fieldname": "email_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Email Address", + "options": "Email", + "reqd": 1 + }, + { + "depends_on": "enable", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Google Contacts" + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "authorize_google_contacts_access", + "fieldtype": "Button", + "label": "Authorize Google Contacts Access" + } + ], + "modified": "2019-06-19 15:26:24.494101", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Google Contacts", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py new file mode 100644 index 0000000000..67f1baf30a --- /dev/null +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import requests +from frappe.model.document import Document +from frappe import _ +from frappe.utils import get_request_site_address + +SCOPES = "https://www.googleapis.com/auth/contacts" +REQUEST = "https://people.googleapis.com/v1/people/me/connections" +PARAMS = {"personFields": "names,emailAddresses,organizations,phoneNumbers"} + +class GoogleContacts(Document): + + def validate(self): + if not frappe.db.get_single_value("Google Settings", "enable"): + frappe.throw(_("Enable Google API in Google Settings.")) + + def get_access_token(self): + google_settings = frappe.get_doc("Google Settings") + + if not google_settings.enable: + frappe.throw(_("Google Contacts Integration is disabled.")) + + if not self.refresh_token: + button_label = frappe.bold(_('Allow Google Contacts Access')) + raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) + + data = { + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "refresh_token": self.get_password(fieldname="refresh_token", raise_exception=False), + "grant_type": "refresh_token", + "scope": SCOPES + } + + try: + r = requests.post("https://www.googleapis.com/oauth2/v4/token", data=data).json() + except requests.exceptions.HTTPError: + button_label = frappe.bold(_('Allow Google Contacts Access')) + frappe.throw(_("Something went wrong during the token generation. Click on {0} to generate a new one.").format(button_label)) + + return r.get("access_token") + +@frappe.whitelist() +def authorize_access(g_contact, reauthorize=None): + """ + If no Authorization code get it from Google and then request for Refresh Token. + Google Contact Name is set to flags to set_value after Authorization Code is obtained. + """ + + google_settings = frappe.get_doc("Google Settings") + google_contact = frappe.get_doc("Google Contacts", g_contact) + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.google_contacts.google_contacts.google_callback" + + if not google_contact.authorization_code or reauthorize: + frappe.cache().hset("google_contacts", "google_contact", google_contact.name) + return google_callback(client_id=google_settings.client_id, redirect_uri=redirect_uri) + else: + try: + data = { + "code": google_contact.authorization_code, + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "redirect_uri": redirect_uri, + "grant_type": "authorization_code" + } + r = requests.post("https://www.googleapis.com/oauth2/v4/token", data=data).json() + + if "refresh_token" in r: + frappe.db.set_value("Google Contacts", google_contact.name, "refresh_token", r.get("refresh_token")) + + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = "/desk#Form/Google%20Contacts/{}".format(google_contact.name) + + frappe.msgprint(_("Google Contacts has been configured.")) + except Exception as e: + frappe.throw(e) + +@frappe.whitelist() +def google_callback(client_id=None, redirect_uri=None, code=None): + """ + Authorization code is sent to callback as per the API configuration + """ + if code is None: + return { + "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) + } + else: + google_contact = frappe.cache().hget("google_contacts", "google_contact") + frappe.db.set_value("Google Contacts", google_contact, "authorization_code", code) + + authorize_access(google_contact) + +@frappe.whitelist() +def sync(g_contact=None): + filters = {"enable": 1} + + if g_contact: + filters.update({"name": g_contact}) + + google_contacts = frappe.get_list("Google Contacts", filters=filters) + + for google_contact in google_contacts: + doc = frappe.get_doc("Google Contacts", google_contact.name) + access_token = doc.get_access_token() + + headers = {"Authorization": "Bearer {}".format(access_token)} + + try: + r = requests.get(REQUEST, headers=headers, params=PARAMS) + except Exception as e: + frappe.throw(e) + + try: + r = r.json() + except Exception as e: + # if request doesn't return json show HTML ask permissions or to identify the error on google side + frappe.throw(e) + + connections = r.get("connections") + contacts_updated = 0 + + frappe.db.set_value("Google Contacts", doc.name, "last_sync_on", frappe.utils.now_datetime()) + + if connections: + for idx, connection in enumerate(connections): + frappe.publish_realtime('import_google_contacts', dict(progress=idx+1, total=r.get("totalPeople")), user=frappe.session.user) + + for name in connection.get("names"): + if name.get("metadata").get("primary"): + for email in connection.get("emailAddresses"): + if not frappe.db.exists("Contact", {"email_id": email.get("value")}): + contacts_updated += 1 + + 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.") + + if g_contact: + return _("No Google Contacts present to sync.") # If no Google Contacts to sync + +def get_indexed_value(d, index, key): + if not d: + return "" + + try: + return d[index].get(key) + except IndexError: + return "" diff --git a/frappe/integrations/doctype/google_settings/__init__.py b/frappe/integrations/doctype/google_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/google_settings/google_settings.js b/frappe/integrations/doctype/google_settings/google_settings.js new file mode 100644 index 0000000000..488cb05b9b --- /dev/null +++ b/frappe/integrations/doctype/google_settings/google_settings.js @@ -0,0 +1,9 @@ +// Copyright (c) 2019, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Google Settings', { + enable: function(frm) { + frm.set_df_property('client_id', 'reqd', frm.doc.enable ? 1 : 0); + frm.set_df_property('client_secret', 'reqd', frm.doc.enable ? 1 : 0); + } +}); diff --git a/frappe/integrations/doctype/google_settings/google_settings.json b/frappe/integrations/doctype/google_settings/google_settings.json new file mode 100644 index 0000000000..c61d6606b5 --- /dev/null +++ b/frappe/integrations/doctype/google_settings/google_settings.json @@ -0,0 +1,69 @@ +{ + "creation": "2019-06-14 00:08:37.255003", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable", + "google_credentials", + "client_id", + "client_secret" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "google_credentials", + "fieldtype": "Section Break", + "label": "Google Credentials" + }, + { + "fieldname": "client_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Client ID" + }, + { + "fieldname": "client_secret", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Client Secret" + } + ], + "issingle": 1, + "modified": "2019-06-19 15:28:05.957380", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Google Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_settings/google_settings.py b/frappe/integrations/doctype/google_settings/google_settings.py new file mode 100644 index 0000000000..50713f187c --- /dev/null +++ b/frappe/integrations/doctype/google_settings/google_settings.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 GoogleSettings(Document): + pass \ No newline at end of file