From b9562b00eefcd71529b4a1c73e718477ab986a19 Mon Sep 17 00:00:00 2001 From: strixaluco Date: Thu, 12 Jan 2017 13:53:02 +0800 Subject: [PATCH 01/14] [minor] login page typo fix (#2580) --- frappe/www/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/login.html b/frappe/www/login.html index a516cc5ac2..29e9da34ad 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -64,7 +64,7 @@ {%- if not disable_signup -%}

- {{ _("Dont have an account? Sign up") }} + {{ _("Don't have an account? Sign up") }}

{%- endif -%}

From 31f978e4edc8f460ba6cf762aabc217283e7b13c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 12 Jan 2017 11:24:24 +0530 Subject: [PATCH 02/14] [Print Settings] Allow page break inside tables (#2565) --- .../print_settings/print_settings.json | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/frappe/print/doctype/print_settings/print_settings.json b/frappe/print/doctype/print_settings/print_settings.json index 0b59254abc..7415dc7947 100644 --- a/frappe/print/doctype/print_settings/print_settings.json +++ b/frappe/print/doctype/print_settings/print_settings.json @@ -465,6 +465,34 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allow_page_break_inside_tables", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow page break inside tables", + "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, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -530,7 +558,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-12-29 14:40:32.410139", + "modified": "2017-01-10 16:48:51.244664", "modified_by": "Administrator", "module": "Print", "name": "Print Settings", From 76e41feac76307e6e6b6b09813483865f74706cd Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 12 Jan 2017 11:25:21 +0530 Subject: [PATCH 03/14] [minor][fix] #6875 (#2569) --- .../integrations/doctype/dropbox_settings/dropbox_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index e6ca9f04bb..53f1ca142a 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -131,6 +131,7 @@ def get_dropbox_authorize_url(): @frappe.whitelist(allow_guest=True) def dropbox_callback(oauth_token=None, not_approved=False): doc = frappe.get_doc("Dropbox Settings") + close = '

' + _('Please close this window') + '

' if not not_approved: if doc.get_password(fieldname="dropbox_access_key", raise_exception=False)==oauth_token: @@ -145,7 +146,6 @@ def dropbox_callback(oauth_token=None, not_approved=False): frappe.db.commit() else: - close = '

' + _('Please close this window') + '

' frappe.respond_as_web_page(_("Dropbox Setup"), _("Illegal Access Token. Please try again") + close, success=False, http_status_code=frappe.AuthenticationError.http_status_code) From 8c0a67cde8db4f90bd29f9fc956dd2a8977ff97f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 12 Jan 2017 11:26:14 +0530 Subject: [PATCH 04/14] [fix] make tree params which includs custom mandatory params (#2571) --- frappe/desk/treeview.py | 29 ++++++++++++++--------- frappe/public/js/frappe/views/treeview.js | 1 - 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 6b0cef4ec3..ac35841d4c 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -46,17 +46,24 @@ def get_children(): @frappe.whitelist() def add_node(): - doctype = frappe.form_dict.get('doctype') - parent_field = 'parent_' + doctype.lower().replace(' ', '_') - name_field = doctype.lower().replace(' ', '_') + '_name' + args = make_tree_args(**frappe.form_dict) + doc = frappe.get_doc(args) - doc = frappe.new_doc(doctype) - doc.update({ - name_field: frappe.form_dict[name_field], - parent_field: frappe.form_dict['parent'], - "is_group": frappe.form_dict['is_group'] - }) - if doctype == "Sales Person": + if args.doctype == "Sales Person": doc.employee = frappe.form_dict.get('employee') - doc.save() \ No newline at end of file + doc.save() + +def make_tree_args(**kwarg): + del kwarg['cmd'] + + doctype = kwarg['doctype'] + parent_field = 'parent_' + doctype.lower().replace(' ', '_') + name_field = doctype.lower().replace(' ', '_') + '_name' + + kwarg.update({ + name_field: kwarg[name_field], + parent_field: kwarg["parent"] + }) + + return frappe._dict(kwarg) diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 3cde94db3c..a9e8b7d27f 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -214,7 +214,6 @@ frappe.views.TreeView = Class.extend({ } $.extend(args, v) - return frappe.call({ method: me.opts.add_tree_node || "frappe.desk.treeview.add_node", args: args, From 4c6f483b06733f6ca3f7af9bfe12d51d5d946592 Mon Sep 17 00:00:00 2001 From: Kanchan Chauhan Date: Wed, 11 Jan 2017 19:09:58 +0530 Subject: [PATCH 05/14] Date picker fixes for filter selection --- frappe/public/js/frappe/form/control.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 78ef5c0ceb..b0f79564ef 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -735,11 +735,11 @@ frappe.ui.form.ControlDateRange = frappe.ui.form.ControlData.extend({ this.set_mandatory && this.set_mandatory(value); }, parse: function(value) { - if(value) { - vals = value.split(",") - value = dateutil.user_to_obj(vals[0]); - value2 = dateutil.user_to_obj(vals[vals.length-1]); - return [value,value2]; + if(value && (value.indexOf(',') !== -1 || value.indexOf('to') !== -1)) { + vals = value.split(/[( to )(,)]/) + from_date = moment(dateutil.user_to_obj(vals[0])).format('YYYY-MM-DD'); + to_date = moment(dateutil.user_to_obj(vals[vals.length-1])).format('YYYY-MM-DD'); + return [from_date, to_date]; } }, format_for_input: function(value,value2) { From d3d957c10a72c7b79c63fdfe4cfacc7750d0ebc6 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 12 Jan 2017 13:06:47 +0530 Subject: [PATCH 06/14] [fix] AssignToDialog in ListView frappe/erpnext#7459 (#2584) --- frappe/public/js/frappe/list/doclistview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/doclistview.js b/frappe/public/js/frappe/list/doclistview.js index 0e73416ea0..d2108ddb94 100644 --- a/frappe/public/js/frappe/list/doclistview.js +++ b/frappe/public/js/frappe/list/doclistview.js @@ -663,7 +663,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ }) if(docname.length >= 1){ - me.dialog = new frappe.ui.AssignToDialog({ + me.dialog = new frappe.ui.form.AssignToDialog({ obj: me, method: 'frappe.desk.form.assign_to.add_multiple', doctype: me.doctype, From 4dfd10b0e664a77c474be70f8e9f74b9983414db Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 12 Jan 2017 14:37:45 +0530 Subject: [PATCH 07/14] remove super from help article (#2585) --- frappe/website/doctype/help_article/help_article.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/website/doctype/help_article/help_article.py b/frappe/website/doctype/help_article/help_article.py index 7f48a566ee..76785577aa 100644 --- a/frappe/website/doctype/help_article/help_article.py +++ b/frappe/website/doctype/help_article/help_article.py @@ -26,7 +26,6 @@ class HelpArticle(WebsiteGenerator): def on_update(self): self.update_category() clear_cache() - super(HelpArticle, self).on_update() def update_category(self): cnt = frappe.db.sql("""select count(*) from `tabHelp Article` From f3923e34f2cf14b131fcee7f268a3dd218f5a866 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 12 Jan 2017 15:56:34 +0530 Subject: [PATCH 08/14] [fix] totals for saved reports, fixes frappe/erpnext#7424 --- frappe/public/js/frappe/views/reports/reportview.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index f5fc0b482d..bd07b52b60 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -221,7 +221,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ if(opts.sort_by_next) this.sort_by_next_select.val(opts.sort_by_next); if(opts.sort_order_next) this.sort_order_next_select.val(opts.sort_order_next); - this.add_totals_row = opts.add_totals_row; + this.add_totals_row = cint(opts.add_totals_row); }, set_route_filters: function(first_load) { @@ -711,6 +711,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ sort_order: me.sort_order_select.val(), sort_by_next: me.sort_by_next_select.val(), sort_order_next: me.sort_order_next_select.val(), + add_totals_row: me.add_totals_row }) }, callback: function(r) { From ab3e8454607ca362fda699e2c834aed347fa5c46 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 12 Jan 2017 16:29:23 +0530 Subject: [PATCH 09/14] [fix] patch --- .../patches/v7_2/set_in_standard_filter_property.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/patches/v7_2/set_in_standard_filter_property.py b/frappe/patches/v7_2/set_in_standard_filter_property.py index 85abda4008..421861cd69 100644 --- a/frappe/patches/v7_2/set_in_standard_filter_property.py +++ b/frappe/patches/v7_2/set_in_standard_filter_property.py @@ -2,11 +2,14 @@ import frappe def execute(): frappe.reload_doc('custom', 'doctype', 'custom_field', force=True) - + try: frappe.db.sql('update `tabCustom Field` set in_standard_filter = in_filter_dash') except Exception, e: if e.args[0]!=1054: raise e - - for doctype in frappe.get_all("DocType", {"istable": 0, "issingle": 0}): - frappe.reload_doctype(doctype.name, force=True) \ No newline at end of file + + for doctype in frappe.get_all("DocType", {"istable": 0, "issingle": 0, "custom": 0}): + try: + frappe.reload_doctype(doctype.name, force=True) + except KeyError: + pass \ No newline at end of file From 25349860bbd0d66dab7b322742ae1efb2a9ec59c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 13 Jan 2017 11:21:54 +0530 Subject: [PATCH 10/14] [Fix] User not able to assign the transaction --- frappe/public/js/frappe/form/footer/assign_to.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/footer/assign_to.js b/frappe/public/js/frappe/form/footer/assign_to.js index 60b3627550..a4dca37538 100644 --- a/frappe/public/js/frappe/form/footer/assign_to.js +++ b/frappe/public/js/frappe/form/footer/assign_to.js @@ -155,7 +155,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ primary_action: function() { var assign_to = opts.obj.dialog.fields_dict.assign_to.get_value(); var args = opts.obj.dialog.get_values(); - frappe.ui.add_assignment(assign_to, args, opts, dialog); + frappe.ui.add_assignment(assign_to, args, opts, opts.obj.dialog); }, primary_action_label: __("Add") })); From 4ac1863027b44563b01112198b94de1ddfe843e8 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 12 Jan 2017 16:29:23 +0530 Subject: [PATCH 11/14] [fix] patch --- .../patches/v7_2/set_in_standard_filter_property.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/patches/v7_2/set_in_standard_filter_property.py b/frappe/patches/v7_2/set_in_standard_filter_property.py index 85abda4008..421861cd69 100644 --- a/frappe/patches/v7_2/set_in_standard_filter_property.py +++ b/frappe/patches/v7_2/set_in_standard_filter_property.py @@ -2,11 +2,14 @@ import frappe def execute(): frappe.reload_doc('custom', 'doctype', 'custom_field', force=True) - + try: frappe.db.sql('update `tabCustom Field` set in_standard_filter = in_filter_dash') except Exception, e: if e.args[0]!=1054: raise e - - for doctype in frappe.get_all("DocType", {"istable": 0, "issingle": 0}): - frappe.reload_doctype(doctype.name, force=True) \ No newline at end of file + + for doctype in frappe.get_all("DocType", {"istable": 0, "issingle": 0, "custom": 0}): + try: + frappe.reload_doctype(doctype.name, force=True) + except KeyError: + pass \ No newline at end of file From e0c2163aafdb239de1d390f5189e6fc54e6fae61 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 13 Jan 2017 15:03:23 +0600 Subject: [PATCH 12/14] bumped to version 7.2.9 --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index bb6f8aed6f..d9fa2a7b05 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -13,7 +13,7 @@ import os, sys, importlib, inspect, json from .exceptions import * from .utils.jinja import get_jenv, get_template, render_template -__version__ = '7.2.8' +__version__ = '7.2.9' __title__ = "Frappe Framework" local = Local() From 48dc91a59f4b96e665f274d0963375245aaaf981 Mon Sep 17 00:00:00 2001 From: robert schouten Date: Fri, 13 Jan 2017 17:15:11 +0800 Subject: [PATCH 13/14] Email inbox (#2485) * Communication_reconciliation * Email inbox * added collapsed timeline for communication * new email prefill from when only one address * timeline duplication and notifcation and stability fixs * add subject to timeline * email system v7 fixes * added password required validation * use proper email datetime * email inbox v7.1 fixes * email inbox communication_date used instead of created * email inbox communication data cleanup * remove old patch * email inbox permit unlinking * [fix] duplication queue and rename domain to email domain * fix rename domain * email inbox index optimisation * email inbox relink doctype query usability fix * email_inbox Set attachment variable for forwards and replies based on whether it exists or not * uidnext fixes and cleanup * email inbox fix tests * fix rebase timeline issue * email inbox threading and cleanup * email inbox cleanup * bring inbox into frappe and cleanup * fix missed path * inbox cleanup * email-inbox move service back to account * separate to erpnext contact match * email inbox cleanup and move relink and timeline fix * relink optimisation * inbox fix footer * keep email drafts on pages not just forms * email inbox add sent items * email inbox allow not save list settings on query * email inbox cleanup and remove communications recon * add suspend sending button in email queue * fix rebase error * email inbox assorted fixes * email inbox fixes and relink option only when linked otherwise link; * email inbox fix sender fullname as administrator * create user email to custom button * email inbox sent no match and seen * email inbox fix unicode issues with subject recipient and sender --- frappe/__init__.py | 4 +- frappe/config/desktop.py | 8 + frappe/config/setup.py | 5 + .../doctype/communication/communication.json | 308 ++++++++- .../doctype/communication/communication.py | 39 +- frappe/core/doctype/communication/email.py | 48 +- frappe/core/doctype/user/user.js | 44 ++ frappe/core/doctype/user/user.json | 34 +- frappe/core/doctype/user/user.py | 75 +++ frappe/core/doctype/user_email/__init__.py | 0 .../core/doctype/user_email/user_email.json | 124 ++++ frappe/core/doctype/user_email/user_email.py | 10 + frappe/desk/form/load.py | 5 +- frappe/desk/page/activity/activity_row.html | 2 + frappe/desk/reportview.py | 5 +- frappe/email/__init__.py | 70 ++ .../doctype/email_account/email_account.js | 83 ++- .../doctype/email_account/email_account.json | 318 +++++++-- .../doctype/email_account/email_account.py | 252 +++++-- .../email_account/test_email_account.py | 6 +- .../doctype/email_account/test_records.json | 8 +- frappe/email/doctype/email_domain/__init__.py | 0 .../doctype/email_domain/email_domain.js | 16 + .../doctype/email_domain/email_domain.json | 443 ++++++++++++ .../doctype/email_domain/email_domain.py | 80 +++ .../doctype/email_domain/test_email_domain.py | 12 + .../doctype/email_flag_queue/__init__.py | 0 .../email_flag_queue/email_flag_queue.js | 8 + .../email_flag_queue/email_flag_queue.json | 144 ++++ .../email_flag_queue/email_flag_queue.py | 10 + .../email_flag_queue/test_email_flag_queue.py | 12 + .../doctype/email_queue/email_queue_list.js | 17 + .../email/doctype/unhandled_email/__init__.py | 0 .../unhandled_email/test_unhandled_email.py | 12 + .../unhandled_email/unhandled_email.json | 204 ++++++ .../unhandled_email/unhandled_email.py | 11 + frappe/email/page/__init__.py | 0 frappe/email/page/email_inbox/__init__.py | 119 ++++ frappe/email/page/email_inbox/email_inbox.js | 636 ++++++++++++++++++ .../email/page/email_inbox/email_inbox.json | 23 + .../email/page/email_inbox/inbox_email.html | 60 ++ .../page/email_inbox/inbox_email_actions.html | 11 + .../email/page/email_inbox/inbox_filter.html | 8 + .../email/page/email_inbox/inbox_footer.html | 9 + .../email/page/email_inbox/inbox_headers.html | 27 + frappe/email/page/email_inbox/inbox_list.html | 46 ++ frappe/email/queue.py | 15 +- frappe/email/receive.py | 488 ++++++++++++-- frappe/model/base_document.py | 2 +- frappe/patches.txt | 5 + frappe/patches/v6_24/imap_get_unique.py | 82 +++ frappe/patches/v7_1/fix_email_sender.py | 12 + .../patches/v7_2/match_emails_to_contacts.py | 72 ++ frappe/public/js/frappe/desk.js | 85 +++ .../public/js/frappe/form/footer/timeline.js | 88 ++- .../js/frappe/form/footer/timeline_item.html | 54 +- .../public/js/frappe/views/communication.js | 116 +++- .../public/js/lib/bootstrap-paginator.min.js | 1 + frappe/tests/test_password.py | 1 + frappe/utils/__init__.py | 5 +- frappe/utils/install.py | 5 +- 61 files changed, 4078 insertions(+), 309 deletions(-) create mode 100644 frappe/core/doctype/user_email/__init__.py create mode 100644 frappe/core/doctype/user_email/user_email.json create mode 100644 frappe/core/doctype/user_email/user_email.py create mode 100644 frappe/email/doctype/email_domain/__init__.py create mode 100644 frappe/email/doctype/email_domain/email_domain.js create mode 100644 frappe/email/doctype/email_domain/email_domain.json create mode 100644 frappe/email/doctype/email_domain/email_domain.py create mode 100644 frappe/email/doctype/email_domain/test_email_domain.py create mode 100644 frappe/email/doctype/email_flag_queue/__init__.py create mode 100644 frappe/email/doctype/email_flag_queue/email_flag_queue.js create mode 100644 frappe/email/doctype/email_flag_queue/email_flag_queue.json create mode 100644 frappe/email/doctype/email_flag_queue/email_flag_queue.py create mode 100644 frappe/email/doctype/email_flag_queue/test_email_flag_queue.py create mode 100644 frappe/email/doctype/unhandled_email/__init__.py create mode 100644 frappe/email/doctype/unhandled_email/test_unhandled_email.py create mode 100644 frappe/email/doctype/unhandled_email/unhandled_email.json create mode 100644 frappe/email/doctype/unhandled_email/unhandled_email.py create mode 100644 frappe/email/page/__init__.py create mode 100644 frappe/email/page/email_inbox/__init__.py create mode 100644 frappe/email/page/email_inbox/email_inbox.js create mode 100644 frappe/email/page/email_inbox/email_inbox.json create mode 100644 frappe/email/page/email_inbox/inbox_email.html create mode 100644 frappe/email/page/email_inbox/inbox_email_actions.html create mode 100644 frappe/email/page/email_inbox/inbox_filter.html create mode 100644 frappe/email/page/email_inbox/inbox_footer.html create mode 100644 frappe/email/page/email_inbox/inbox_headers.html create mode 100644 frappe/email/page/email_inbox/inbox_list.html create mode 100644 frappe/patches/v6_24/imap_get_unique.py create mode 100644 frappe/patches/v7_1/fix_email_sender.py create mode 100644 frappe/patches/v7_2/match_emails_to_contacts.py create mode 100644 frappe/public/js/lib/bootstrap-paginator.min.js diff --git a/frappe/__init__.py b/frappe/__init__.py index a7370b615d..8b30f7da0e 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -372,7 +372,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, attachments=None, content=None, doctype=None, name=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, - send_priority=1, communication=None, retry=1, now=None): + send_priority=1, communication=None, retry=1, now=None, read_receipt=None): """Send email using user's default **Email Account** or global default **Email Account**. @@ -411,7 +411,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to, send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, - communication=communication, now=now) + communication=communication, now=now, read_receipt=read_receipt) whitelisted = [] guest_methods = [] diff --git a/frappe/config/desktop.py b/frappe/config/desktop.py index 4859802f49..97c4edefdb 100644 --- a/frappe/config/desktop.py +++ b/frappe/config/desktop.py @@ -3,6 +3,14 @@ from frappe import _ def get_data(): return [ + { + "module_name": "Email", + "color": "grey", + "icon": "octicon octicon-mail", + "type": "page", + "link": "email_inbox", + "label": _("Email Inbox") + }, { "module_name": "Desk", "label": _("Tools"), diff --git a/frappe/config/setup.py b/frappe/config/setup.py index 7e39a79675..45c3667a11 100644 --- a/frappe/config/setup.py +++ b/frappe/config/setup.py @@ -134,6 +134,11 @@ def get_data(): "name": "Email Account", "description": _("Add / Manage Email Accounts.") }, + { + "type": "doctype", + "name": "Email Domain", + "description": _("Add / Manage Email Domains.") + }, { "type": "doctype", "name": "Email Alert", diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index 4886b6ad18..c968b67404 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -113,7 +113,7 @@ "in_list_view": 0, "in_standard_filter": 0, "label": "From", - "length": 0, + "length": 255, "no_copy": 0, "options": "Email", "permlevel": 0, @@ -428,7 +428,7 @@ "label": "Comment Type", "length": 0, "no_copy": 0, - "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nUpdated\nSubmitted\nCancelled\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot", + "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nUpdated\nSubmitted\nCancelled\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot\nRelinked", "permlevel": 0, "precision": "", "print_hide": 0, @@ -559,7 +559,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "Today", + "default": "Now", "fieldname": "communication_date", "fieldtype": "Datetime", "hidden": 0, @@ -586,7 +586,32 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "fieldname": "read_receipt", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_standard_filter": 0, + "in_list_view": 0, + "label": "Sent Read Receipt", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, "fieldname": "column_break_14", "fieldtype": "Column Break", "hidden": 0, @@ -988,6 +1013,32 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fieldname": "timeline_label", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Timeline field Name", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, "default": "0", "fieldname": "unread_notification_sent", "fieldtype": "Check", @@ -1094,6 +1145,231 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "fieldname": "email_inbox", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Email Inbox", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "uid", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "UID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "message_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_list_view": 0, + "label": "Message ID", + "length": 995, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "unique_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Unique id", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "deleted", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Deleted", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "actualdate", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "ActualDate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "nomatch", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "No Match", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "has_attachment", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Has Attachment", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "timeline_hide", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Hidden in Timeline", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -1140,6 +1416,27 @@ "cancel": 0, "create": 1, "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + }, + { + "amend": 0, + "apply_user_permissions": 1, + "cancel": 0, + "create": 0, + "delete": 0, "email": 0, "export": 0, "if_owner": 1, @@ -1153,6 +1450,7 @@ "set_user_permissions": 0, "share": 0, "submit": 0, + "user_permission_doctypes": "[\"Email Account\"]", "write": 1 } ], @@ -1164,4 +1462,4 @@ "title_field": "subject", "track_changes": 1, "track_seen": 0 -} \ No newline at end of file +} diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 71470457a4..34f3c6af83 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -48,25 +48,25 @@ class Communication(Document): def after_insert(self): if not (self.reference_doctype and self.reference_name): return + if not self.timeline_hide: + if self.communication_type in ("Communication", "Comment"): + # send new comment to listening clients + frappe.publish_realtime('new_communication', self.as_dict(), + doctype=self.reference_doctype, docname=self.reference_name, + after_commit=True) - if self.communication_type in ("Communication", "Comment"): - # send new comment to listening clients - frappe.publish_realtime('new_communication', self.as_dict(), - doctype= self.reference_doctype, docname = self.reference_name, - after_commit=True) + if self.communication_type == "Comment": + notify_mentions(self) - if self.communication_type == "Comment": - notify_mentions(self) - - elif self.communication_type in ("Chat", "Notification", "Bot"): - if self.reference_name == frappe.session.user: - message = self.as_dict() - message['broadcast'] = True - frappe.publish_realtime('new_message', message, after_commit=True) - else: - # reference_name contains the user who is addressed in the messages' page comment - frappe.publish_realtime('new_message', self.as_dict(), - user=self.reference_name, after_commit=True) + elif self.communication_type in ("Chat", "Notification", "Bot"): + if self.reference_name == frappe.session.user: + message = self.as_dict() + message['broadcast'] = True + frappe.publish_realtime('new_message', message, after_commit=True) + else: + # reference_name contains the user who is addressed in the messages' page comment + frappe.publish_realtime('new_message', self.as_dict(), + user=self.reference_name, after_commit=True) def on_update(self): """Update parent status as `Open` or `Replied`.""" @@ -118,7 +118,7 @@ class Communication(Document): sender_name = None self.sender = sender_email - self.sender_full_name = sender_name or get_fullname(frappe.session.user) + self.sender_full_name = sender_name or get_fullname(frappe.session.user) if frappe.session.user!='Administrator' else None def get_parent_doc(self): """Returns document of `reference_doctype`, `reference_doctype`""" @@ -227,8 +227,7 @@ def on_doctype_update(): frappe.db.add_index("Communication", ["timeline_doctype", "timeline_name"]) frappe.db.add_index("Communication", ["link_doctype", "link_name"]) frappe.db.add_index("Communication", ["status", "communication_type"]) - frappe.db.add_index("Communication", ["creation"]) - frappe.db.add_index("Communication", ["modified"]) + frappe.db.add_index("Communication", ["communication_date"]) frappe.db.add_index("Communication", ["message_id(200)"]) def has_permission(doc, ptype, user): diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 7f713284c0..eb4bf2a9c2 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -16,11 +16,12 @@ import MySQLdb import time from frappe import _ from frappe.utils.background_jobs import enqueue +import email.utils @frappe.whitelist() def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", - sender=None, recipients=None, communication_medium="Email", send_email=False, - print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, flags=None): + sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, + print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, flags=None,read_receipt=None): """Make a new communication. :param doctype: Reference DocType. @@ -53,13 +54,15 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "subject": subject, "content": content, "sender": sender, + "sender_full_name":sender_full_name, "recipients": recipients, "cc": cc or None, "communication_medium": communication_medium, "sent_or_received": sent_or_received, "reference_doctype": doctype, "reference_name": name, - "message_id":get_message_id().strip(" <>") + "message_id":get_message_id().strip(" <>"), + "read_receipt":read_receipt }) comm.insert(ignore_permissions=True) @@ -151,7 +154,8 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, message_id=doc.message_id, unsubscribe_message=unsubscribe_message, delayed=True, - communication=doc.name + communication=doc.name, + read_receipt = doc.read_receipt ) def update_parent_status(doc): @@ -196,8 +200,9 @@ def get_recipients_and_cc(doc, recipients, cc, fetched_from_email_account=False) original_recipients, recipients = recipients, [] # send email to the sender of the previous email in the thread which this email is a reply to - if doc.previous_email_sender: - recipients.append(doc.previous_email_sender) + #provides erratic results and can send external + #if doc.previous_email_sender: + # recipients.append(doc.previous_email_sender) # cc that was received in the email original_cc = split_emails(doc.cc) @@ -254,9 +259,13 @@ def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None) def set_incoming_outgoing_accounts(doc): doc.incoming_email_account = doc.outgoing_email_account = None - if doc.reference_doctype: + if not doc.incoming_email_account and doc.sender: doc.incoming_email_account = frappe.db.get_value("Email Account", - {"append_to": doc.reference_doctype, "enable_incoming": 1}, "email_id") + {"email_id": doc.sender, "enable_incoming": 1}, "email_id") + + if not doc.incoming_email_account and doc.reference_doctype: + doc.incoming_email_account = frappe.db.get_value("Email Account", + {"append_to": doc.reference_doctype, }, "email_id") doc.outgoing_email_account = frappe.db.get_value("Email Account", {"append_to": doc.reference_doctype, "enable_outgoing": 1}, @@ -276,20 +285,13 @@ def get_recipients(doc, fetched_from_email_account=False): # [EDGE CASE] doc.recipients can be None when an email is sent as BCC recipients = split_emails(doc.recipients) - if fetched_from_email_account and doc.in_reply_to: + #if fetched_from_email_account and doc.in_reply_to: # add sender of previous reply - doc.previous_email_sender = frappe.db.get_value("Communication", doc.in_reply_to, "sender") - recipients.append(doc.previous_email_sender) + #doc.previous_email_sender = frappe.db.get_value("Communication", doc.in_reply_to, "sender") + #recipients.append(doc.previous_email_sender) if recipients: - # exclude email accounts - exclude = [d[0] for d in - frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] - exclude += [d[0] for d in - frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True) - if d[0]] - - recipients = filter_email_list(doc, recipients, exclude) + recipients = filter_email_list(doc, recipients, []) return recipients @@ -308,12 +310,8 @@ def get_cc(doc, recipients=None, fetched_from_email_account=False): cc.append(doc.sender) if cc: - # exclude email accounts, unfollows, recipients and unsubscribes - exclude = [d[0] for d in - frappe.db.get_all("Email Account", ["email_id"], {"enable_incoming": 1}, as_list=True)] - exclude += [d[0] for d in - frappe.db.get_all("Email Account", ["login_id"], {"enable_incoming": 1}, as_list=True) - if d[0]] + # exclude unfollows, recipients and unsubscribes + exclude = [] #added to remove account check exclude += [d[0] for d in frappe.db.get_all("User", ["name"], {"thread_notify": 0}, as_list=True)] exclude += [(parseaddr(email)[1] or "").lower() for email in recipients] diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index ad660a8b59..05de1d2e06 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -76,6 +76,26 @@ frappe.ui.form.on('User', { } } } + if (frm.doc.user_emails){ + var found =0; + for (var i = 0;i" + __(feed_type) + "", feed_type==="Deleted" ? subject : link ]) %} {% } else if (feed_type==="Updated") { %} {%= __("Updated {0}: {1}", [link, "" + subject + ""]) %} + {% } else if (feed_type==="Relinked") { %} + {%= __("{0} {1} to {2}", [by, content,link]) %} {% } else if (reference_doctype && reference_name) { %} {%= __("{0}: {1}", [link, "" + content + ""]) %} {% } else { %} diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 80b50ac069..5f5bbe7c36 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -12,7 +12,6 @@ from frappe import _ @frappe.whitelist() def get(): args = get_form_params() - args.save_list_settings = True data = compress(execute(**args), args = args) @@ -33,6 +32,10 @@ def get_form_params(): data["fields"] = json.loads(data["fields"]) if isinstance(data.get("docstatus"), basestring): data["docstatus"] = json.loads(data["docstatus"]) + if isinstance(data.get("save_list_settings"), basestring): + data["save_list_settings"] = json.loads(data["save_list_settings"]) + else: + data["save_list_settings"] = True # queries must always be server side diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index eeb7823172..626efd258a 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -34,3 +34,73 @@ def get_system_managers(): WHERE role='System Manager' AND parent!='Administrator' AND parent IN (SELECT email FROM tabUser WHERE enabled=1)""") + +@frappe.whitelist() +def relink(name,reference_doctype=None,reference_name=None): + dt = reference_doctype + dn = reference_name + origin = frappe.db.get_value("Communication", name, ["reference_doctype", "reference_name", "communication_medium", "subject"], as_dict=1) + + subject_link = '' + origin.subject + content= 'Relinked ' + origin.communication_medium + ' ' + subject_link + '' + + if origin.reference_doctype: + from_link = '' + content += ' from ' + from_link + origin.reference_doctype +' '+ origin.reference_name +'' + + frappe.db.sql("""UPDATE `tabCommunication` + SET reference_doctype = %(ref_doc)s, reference_name = %(ref_name)s, STATUS = "Linked" + WHERE communication_type = "Communication" and name = %(name)s OR timeline_hide = %(name)s""", + {'ref_doc': dt, + 'ref_name': dn, 'name': name}) + + dup_list = [{"name":name, "timeline_label":False}] + frappe.db.get_values("Communication", {"timeline_hide": name, "communication_type":"Communication"}, ["name", "timeline_label"], as_dict=1) + for comm in dup_list: + if not comm["timeline_label"]: + doc = frappe.get_doc("Communication", comm["name"]) + if not doc.timeline_label: + doc.timeline_doctype = None + doc.timeline_name = None + doc.set_timeline_doc() + if comm["name"] == dup_list[0]["name"]: + doc.save(ignore_permissions=True) + else: + doc.db_update() + + frappe.get_doc({ + "doctype": "Communication", + "communication_type": "Comment", + "comment_type": "Relinked", + "reference_doctype": dt, + "reference_name": dn, + "subject": origin.subject, + "communication_medium": frappe.db.get_value("Communication",name,"communication_medium"), + "reference_owner": frappe.db.get_value(dt, dn, "owner"), + "content": content, + "sender":frappe.session.user + }).insert(ignore_permissions=True) + +def get_communication_doctype(doctype, txt, searchfield, start, page_len, filters): + user_perms = frappe.utils.user.UserPermissions(frappe.session.user) + user_perms.build_permissions() + can_read = user_perms.can_read + from frappe.modules import load_doctype_module + com_doctypes = [] + if len(txt)<2: + + for name in ["Customer", "Supplier"]: + try: + module = load_doctype_module(name, suffix='_dashboard') + if hasattr(module, 'get_data'): + for i in module.get_data()['transactions']: + com_doctypes += i["items"] + except ImportError: + pass + else: + com_doctypes = [d[0] for d in frappe.db.get_values("DocType", {"issingle": 0, "istable": 0, "hide_toolbar": 0})] + + out = [] + for dt in com_doctypes: + if txt.lower().replace("%", "") in dt.lower() and dt in can_read: + out.append([dt]) + return out diff --git a/frappe/email/doctype/email_account/email_account.js b/frappe/email/doctype/email_account/email_account.js index d370144b66..d639fa97fb 100644 --- a/frappe/email/doctype/email_account/email_account.js +++ b/frappe/email/doctype/email_account/email_account.js @@ -8,10 +8,10 @@ frappe.email_defaults = { "use_tls": 1 }, "Outlook.com": { - "email_server": "pop3.live.com", + "email_server": "pop3-mail.outlook.com", "use_ssl": 1, "enable_outgoing": 1, - "smtp_server": "smtp.live.com", + "smtp_server": "smtp-mail.outlook.com", "smtp_port": 587, "use_tls": 1 }, @@ -52,7 +52,7 @@ frappe.email_defaults_imap = { "email_server": "imap.gmail.com" }, "Outlook.com": { - "email_server": "imap.live.com" + "email_server": "imap-mail.outlook.com" }, "Yahoo Mail": { "email_server": "imap.mail.yahoo.com" @@ -88,15 +88,9 @@ frappe.ui.form.on("Email Account", { }); } }, - email_id: function(frm) { - if(!frm.doc.email_account_name) { - frm.set_value("email_account_name", - (frm.doc.service ? frm.doc.service + " " : "") - + toTitle(frm.doc.email_id.split("@")[0].replace(/[._]/g, " "))); - } - }, enable_incoming: function(frm) { - frm.set_df_property("append_to", "reqd", frm.doc.enable_incoming); + frm.doc.no_remaining = null //perform full sync + //frm.set_df_property("append_to", "reqd", frm.doc.enable_incoming); }, notify_if_unreplied: function(frm) { frm.set_df_property("send_notification_to", "reqd", frm.doc.notify_if_unreplied); @@ -105,10 +99,30 @@ frappe.ui.form.on("Email Account", { frm.set_df_property("append_to", "only_select", true); frm.set_query("append_to", "frappe.email.doctype.email_account.email_account.get_append_to"); }, + validate:function(frm){ + frm.events.update_domain(frm,true); + }, refresh: function(frm) { + frm.events.update_domain(frm,true); frm.events.enable_incoming(frm); frm.events.notify_if_unreplied(frm); frm.events.show_gmail_message_for_less_secure_apps(frm); + if (frm.doc.__islocal != 1) { + if (frappe.route_titles["create user account"]) { + var user =frappe.route_titles["create user account"]; + delete frappe.route_titles["create user account"]; + var userdoc = frappe.get_doc("User",user); + frappe.model.with_doc("User", user, function (doc) { + var new_row = frappe.model.add_child(userdoc, "User Email", "user_emails"); + new_row.email_account = cur_frm.doc.name; + new_row.awaiting_password = cur_frm.doc.awaiting_password; + new_row.email_id = cur_frm.doc.email_id; + new_row.idx = 0; + frappe.route_titles["unsaved"] = 1; + frappe.set_route("Form", "User",user); + }); + } + } }, show_gmail_message_for_less_secure_apps: function(frm) { if(frm.doc.service==="Gmail") { @@ -117,4 +131,49 @@ frappe.ui.form.on("Email Account", { href="https://support.google.com/accounts/answer/6010255?hl=en">Read this for details'); } }, -}); + email_id:function(frm){ + //pull domain and if no matching domain go create one + frm.events.update_domain(frm,false); + }, + update_domain:function(frm,norefresh){ + if (cur_frm.doc.email_id && !cur_frm.doc.service) { + frappe.call({ + method: 'get_domain', + doc: cur_frm.doc, + async:false, + args: { + "email_id": cur_frm.doc.email_id + }, + callback: function (frm) { + try { + if (cur_frm.doc.domain !=frm["message"][0]["name"]) { + cur_frm.doc.domain = frm["message"][0]["name"] + cur_frm.doc.email_server= frm["message"][0]["email_server"]; + cur_frm.doc.use_imap= frm["message"][0]["use_imap"]; + cur_frm.doc.smtp_server= frm["message"][0]["smtp_server"]; + cur_frm.doc.use_ssl= frm["message"][0]["use_ssl"]; + cur_frm.doc.use_tls= frm["message"][0]["use_tls"]; + cur_frm.doc.smtp_port = frm["message"][0]["smtp_port"]; + if (!norefresh) { + cur_frm.refresh(); + } + } + } + catch (Exception) { + frappe.confirm( + 'Email Domain not configured for this account\nCreate one?', + function () { + frappe.model.with_doctype("Email Domain", function() { + frappe.route_options = {email_id: cur_frm.doc.email_id}; + frappe.route_titles["return to email_account"] = 1 + var doc = frappe.model.get_new_doc("Email Domain"); + frappe.set_route("Form", "Email Domain", doc.name); + }) + } + ) + } + } + }); + } + } +}); \ No newline at end of file diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index ad6d0641f9..146b682c0d 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -12,63 +12,6 @@ "editable_grid": 0, "engine": "InnoDB", "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "email_settings", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "service", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Service", - "length": 0, - "no_copy": 0, - "options": "\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail", - "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, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -82,10 +25,10 @@ "in_filter": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Email Address", + "label": "Email Id", "length": 0, "no_copy": 0, - "options": "Email", + "options": "", "permlevel": 0, "precision": "", "print_hide": 0, @@ -140,9 +83,10 @@ "in_filter": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Login Id", + "label": "Email Address", "length": 0, "no_copy": 0, + "options": "", "permlevel": 0, "precision": "", "print_hide": 0, @@ -183,6 +127,34 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "awaiting_password", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Awaiting password", + "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, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -212,6 +184,92 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "email_settings", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "domain", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Domain", + "length": 0, + "no_copy": 0, + "options": "Email Domain", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "service", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Service", + "length": 0, + "no_copy": 0, + "options": "\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail", + "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, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -275,7 +333,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "", + "depends_on": "eval: !doc.domain && doc.enable_incoming", "fieldname": "use_imap", "fieldtype": "Check", "hidden": 0, @@ -287,6 +345,7 @@ "label": "Use IMAP", "length": 0, "no_copy": 0, + "options": "domain.use_imap", "permlevel": 0, "precision": "", "print_hide": 0, @@ -304,7 +363,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_incoming", + "depends_on": "eval:!doc.domain && doc.enable_incoming", "description": "e.g. pop.gmail.com / imap.gmail.com", "fieldname": "email_server", "fieldtype": "Data", @@ -317,6 +376,7 @@ "label": "Email Server", "length": 0, "no_copy": 0, + "options": "domain.email_server", "permlevel": 0, "precision": "", "print_hide": 0, @@ -334,7 +394,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_incoming", + "depends_on": "eval:!doc.domain && doc.enable_incoming", "fieldname": "use_ssl", "fieldtype": "Check", "hidden": 0, @@ -346,6 +406,7 @@ "label": "Use SSL", "length": 0, "no_copy": 0, + "options": "domain.use_ssl", "permlevel": 0, "precision": "", "print_hide": 0, @@ -364,7 +425,7 @@ "collapsible": 0, "columns": 0, "default": "1", - "depends_on": "enable_incoming", + "depends_on": "eval:!doc.domain && doc.enable_incoming", "description": "Ignore attachments over this size", "fieldname": "attachment_limit", "fieldtype": "Int", @@ -377,6 +438,7 @@ "label": "Attachment Limit (MB)", "length": 0, "no_copy": 0, + "options": "domain.attachment_limit", "permlevel": 0, "precision": "", "print_hide": 0, @@ -394,6 +456,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "", "depends_on": "enable_incoming", "description": "Append as communication against this DocType (must have fields, \"Status\", \"Subject\")", "fieldname": "append_to", @@ -402,7 +465,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 1, "label": "Append To", "length": 0, @@ -629,7 +692,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_outgoing", + "depends_on": "eval:!doc.domain && doc.enable_outgoing", "description": "e.g. smtp.gmail.com", "fieldname": "smtp_server", "fieldtype": "Data", @@ -642,6 +705,7 @@ "label": "SMTP Server", "length": 0, "no_copy": 0, + "options": "domain.smtp_server", "permlevel": 0, "precision": "", "print_hide": 0, @@ -659,7 +723,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_outgoing", + "depends_on": "eval:!doc.domain && doc.enable_outgoing", "fieldname": "use_tls", "fieldtype": "Check", "hidden": 0, @@ -671,6 +735,7 @@ "label": "Use TLS", "length": 0, "no_copy": 0, + "options": "domain.use_tls", "permlevel": 0, "precision": "", "print_hide": 0, @@ -688,7 +753,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_outgoing", + "depends_on": "eval:!doc.domain && doc.enable_outgoing", "description": "If non standard port (e.g. 587)", "fieldname": "smtp_port", "fieldtype": "Data", @@ -701,6 +766,7 @@ "label": "Port", "length": 0, "no_copy": 0, + "options": "domain.smtp_port", "permlevel": 0, "precision": "", "print_hide": 0, @@ -1029,6 +1095,118 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "uid_validity", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "uid validity", + "length": 0, + "no_copy": 1, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "uidnext", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "uidnext", + "length": 0, + "no_copy": 1, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "no_remaining", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "No of emails remaining to be synced", + "length": 0, + "no_copy": 1, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "no_failed", + "fieldtype": "Int", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "no failed attempts", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -1042,7 +1220,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-12-29 14:39:56.456917", + "modified": "2017-01-06 13:57:39.516766", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -1066,7 +1244,7 @@ "report": 0, "role": "System Manager", "set_user_permissions": 1, - "share": 1, + "share": 0, "submit": 0, "write": 1 } diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index e6915cfb18..90f632c8a3 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -12,7 +12,7 @@ from frappe.utils import validate_email_add, cint, get_datetime, DATE_FORMAT, st from frappe.utils.user import is_system_user from frappe.utils.jinja import render_template from frappe.email.smtp import SMTPServer -from frappe.email.receive import EmailServer, Email +from frappe.email.receive import EmailServer, Email, get_unique_id from poplib import error_proto from dateutil.relativedelta import relativedelta from datetime import datetime, timedelta @@ -31,9 +31,6 @@ class EmailAccount(Document): self.email_account_name = self.email_id.split("@", 1)[0]\ .replace("_", " ").replace(".", " ").replace("-", " ").title() - if self.service: - self.email_account_name = self.email_account_name + " " + self.service - self.name = self.email_account_name def validate(self): @@ -50,15 +47,21 @@ class EmailAccount(Document): if frappe.local.flags.in_patch or frappe.local.flags.in_test: return - if self.enable_incoming and not self.append_to: - frappe.throw(_("Append To is mandatory for incoming mails")) + #if self.enable_incoming and not self.append_to: + # frappe.throw(_("Append To is mandatory for incoming mails")) - if not frappe.local.flags.in_install and not frappe.local.flags.in_patch: - if self.enable_incoming: - self.get_incoming_server() - - if self.enable_outgoing: - self.check_smtp() + if not self.awaiting_password and not frappe.local.flags.in_install and not frappe.local.flags.in_patch: + if self.password: + if self.enable_incoming: + self.get_incoming_server() + self.no_failed = 0 + + + if self.enable_outgoing: + self.check_smtp() + else: + if self.enable_incoming or self.enable_outgoing: + frappe.throw(_("Password is required or select Awaiting Password")) if self.notify_if_unreplied: if not self.send_notification_to: @@ -71,9 +74,20 @@ class EmailAccount(Document): if self.append_to not in valid_doctypes: frappe.throw(_("Append To can be one of {0}").format(comma_or(valid_doctypes))) + + def on_update(self): """Check there is only one default of each type.""" self.there_must_be_only_one_default() + if self.awaiting_password: + # push values to user_emails + frappe.db.sql("""UPDATE `tabUser Email` SET awaiting_password = 1 + WHERE email_account = %(account)s""", {"account": self.name}) + else: + frappe.db.sql("""UPDATE `tabUser Email` SET awaiting_password = 0 + WHERE email_account = %(account)s""", {"account": self.name}) + from frappe.core.doctype.user.user import ask_pass_update + ask_pass_update() def there_must_be_only_one_default(self): """If current Email Account is default, un-default all other accounts.""" @@ -88,6 +102,18 @@ class EmailAccount(Document): email_account.set(fn, 0) email_account.save() + @frappe.whitelist() + def get_domain(self,email_id): + """look-up the domain and then full""" + try: + domain = email_id.split("@") + return frappe.db.sql("""select name,use_imap,email_server,use_ssl,smtp_server,use_tls,smtp_port + from `tabEmail Domain` + where name = %s + """,domain[1],as_dict=1) + except Exception: + pass + def check_smtp(self): """Checks SMTP settings.""" if self.enable_outgoing: @@ -106,12 +132,18 @@ class EmailAccount(Document): def get_incoming_server(self, in_receive=False): """Returns logged in POP3/IMAP connection object.""" + if frappe.cache().get_value("workers:no-internet") == True: + return None args = frappe._dict({ + "email_account":self.name, "host": self.email_server, "use_ssl": self.use_ssl, "username": getattr(self, "login_id", None) or self.email_id, - "use_imap": self.use_imap + "use_imap": self.use_imap, + "uid_validity":self.uid_validity, + "uidnext":self.uidnext, + "no_remaining":self.no_remaining }) if self.password: args.password = self.get_password() @@ -139,38 +171,46 @@ class EmailAccount(Document): if in_receive: # timeout while connecting, see receive.py connect method description = frappe.message_log.pop() if frappe.message_log else "Socket Error" - self.handle_incoming_connect_error(description=description) - + if test_internet(): + self.db_set("no_failed", self.no_failed + 1) + if self.no_failed > 2: + self.handle_incoming_connect_error(description=description) + else: + frappe.cache().set_value("workers:no-internet", True) return None else: raise - + if not in_receive: + if self.use_imap: + email_server.imap.logout() # reset failed attempts count self.set_failed_attempts_count(0) return email_server def handle_incoming_connect_error(self, description): - '''Disable email account if 3 failed attempts found''' - if self.get_failed_attempts_count() == 3: - self.db_set("enable_incoming", 0) - - for user in get_system_managers(only_name=True): - try: - assign_to.add({ - 'assign_to': user, - 'doctype': self.doctype, - 'name': self.name, - 'description': description, - 'priority': 'High', - 'notify': 1 - }) - except assign_to.DuplicateToDoError: - frappe.message_log.pop() - pass + if test_internet(): + if self.get_failed_attempts_count() > 2: + self.db_set("enable_incoming", 0) + + for user in get_system_managers(only_name=True): + try: + assign_to.add({ + 'assign_to': user, + 'doctype': self.doctype, + 'name': self.name, + 'description': description, + 'priority': 'High', + 'notify': 1 + }) + except assign_to.DuplicateToDoError: + frappe.message_log.pop() + pass + else: + self.set_failed_attempts_count(self.get_failed_attempts_count() + 1) else: - self.set_failed_attempts_count(self.get_failed_attempts_count() + 1) + frappe.cache().set_value("workers:no-internet", True) def set_failed_attempts_count(self, value): frappe.cache().set('{0}:email-account-failed-attempts'.format(self.name), value) @@ -181,46 +221,96 @@ class EmailAccount(Document): def receive(self, test_mails=None): """Called by scheduler to receive emails from this EMail account using POP3/IMAP.""" if self.enable_incoming: + exceptions = [] if frappe.local.flags.in_test: incoming_mails = test_mails else: email_server = self.get_incoming_server(in_receive=True) if not email_server: return - - incoming_mails = email_server.get_messages() - - exceptions = [] - for raw in incoming_mails: try: - communication = self.insert_communication(raw) + incoming_mails = email_server.get_messages() + except Exception as e: + frappe.db.sql("update `tabEmail Account` set no_remaining = NULL where name = %s",(email_server.settings.email_account), auto_commit=1) + incoming_mails = [] - except SentEmailInInbox: + for msg in incoming_mails: + try: + + communication = self.insert_communication(msg) + #self.notify_update() + + except SentEmailInInbox as e: frappe.db.rollback() + if self.use_imap: + self.handle_bad_emails(email_server, msg[1], msg[0], "sent email in inbox") - except Exception: + + except Exception as e: frappe.db.rollback() log('email_account.receive') + if self.use_imap: + self.handle_bad_emails(email_server, msg[1], msg[0], frappe.get_traceback()) exceptions.append(frappe.get_traceback()) else: frappe.db.commit() attachments = [d.file_name for d in communication._attachments] - # TODO fix bug where it sends emails to 'Adminsitrator' during testing - communication.notify(attachments=attachments, fetched_from_email_account=True) + if communication.message_id and not communication.timeline_hide: + first = frappe.db.get_value("Communication", {"message_id": communication.message_id},["name"],order_by="creation",as_dict=1) + if first: + if first.name != communication.name: + frappe.db.sql("""update tabCommunication set timeline_hide =%s where name = %s""",(first.name,communication.name),auto_commit=1) + + if self.no_remaining == '0' and not frappe.local.flags.in_test: + if communication.reference_doctype : + if not communication.timeline_hide and not communication.unread_notification_sent: + communication.notify(attachments=attachments, fetched_from_email_account=True) + + #notify if user is linked to account + if len(incoming_mails)>0 and not frappe.local.flags.in_test: + frappe.publish_realtime('new_email', {"account":self.email_account_name,"number":len(incoming_mails)}) if exceptions: raise Exception, frappe.as_json(exceptions) - def insert_communication(self, raw): + def handle_bad_emails(self,email_server,uid,raw,reason): + if cint(email_server.settings.use_imap): + import email + try: + mail = email.message_from_string(raw) + + unique_id = get_unique_id(mail) + message_id = mail.get('Message-ID') + except Exception: + message_id = "can't be parsed" + + unhandled_email = frappe.get_doc({ + "doctype": "Unhandled Email", + "email_account": email_server.settings.email_account, + "uid": uid, + "message_id": message_id, + "unique_id":unique_id, + "reason":reason + }) + unhandled_email.save() + frappe.db.commit() + + def insert_communication(self, msg): + if isinstance(msg,list): + raw, uid, seen = msg + else: + raw = msg + seen = uid = None email = Email(raw) - if email.from_email == self.email_id: + if email.from_email == self.email_id and not email.mail.get("Reply-To"): # gmail shows sent emails in inbox # and we don't want emails sent by us to be pulled back into the system again + # dont count emails sent by the system get those raise SentEmailInInbox - + communication = frappe.get_doc({ "doctype": "Communication", "subject": email.subject, @@ -229,14 +319,23 @@ class EmailAccount(Document): "sent_or_received": "Received", "sender_full_name": email.from_real_name, "sender": email.from_email, - "recipients": email.mail.get("To"), - "cc": email.mail.get("CC"), + "recipients": email.To, + "cc": email.CC, "email_account": self.name, - "communication_medium": "Email" + "communication_medium": "Email", + "uid":uid, + "message_id":email.message_id, + "communication_date":email.date, + "has_attachment": 1 if email.attachments else 0, + "seen":seen, + "unique_id":email.unique_id }) self.set_thread(communication, email) + if not self.no_remaining == '0': + communication.unread_notification_sent = 1 + communication.flags.in_receive = True communication.insert(ignore_permissions = 1) @@ -275,7 +374,7 @@ class EmailAccount(Document): parent = self.find_parent_from_in_reply_to(communication, email) - if not parent: + if not parent and self.append_to: self.set_sender_field_and_subject_field() if not parent and self.append_to: @@ -335,7 +434,7 @@ class EmailAccount(Document): }, fields="name") if parent: - parent = frappe.get_doc(self.append_to, parent[0].name) + parent = frappe._dict(doctype=self.append_to, name=parent[0].name) return parent @@ -378,24 +477,34 @@ class EmailAccount(Document): if in_reply_to and "@{0}".format(frappe.local.site) in in_reply_to: # reply to a communication sent from the system - email_queue = frappe.db.get_value('Email Queue', dict(message_id=in_reply_to), ['reference_doctype', 'reference_name']) + email_queue = frappe.db.get_value('Email Queue', dict(message_id=in_reply_to), ['communication','reference_doctype', 'reference_name']) if email_queue: - parent_doctype, parent_name = email_queue + parent_communication, parent_doctype, parent_name = email_queue + if parent_communication: + communication.in_reply_to = parent_communication else: reference, domain = in_reply_to.split("@", 1) parent_doctype, parent_name = 'Communication', reference if frappe.db.exists(parent_doctype, parent_name): - parent = frappe.get_doc(parent_doctype, parent_name) + parent = frappe._dict(doctype=parent_doctype, name=parent_name) # set in_reply_to of current communication if parent_doctype=='Communication': - communication.in_reply_to = parent_name + # communication.in_reply_to = email_queue.communication if parent.reference_name: # the true parent is the communication parent parent = frappe.get_doc(parent.reference_doctype, parent.reference_name) + if email.message_id: + first = frappe.db.get_value("Communication", {"message_id": email.message_id},["name", "reference_doctype", "reference_name"], order_by="creation", as_dict=1) + + if first: + # set timeline hide to parent doc so are linked + communication.timeline_hide = first.name + if frappe.db.exists(first.reference_doctype, first.reference_name): + parent = frappe._dict(doctype=first.reference_doctype, name=first.reference_name) return parent @@ -438,6 +547,20 @@ def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len if not txt: txt = "" return [[d] for d in frappe.get_hooks("email_append_to") if txt in d] +def test_internet(host="8.8.8.8", port=53, timeout=3): + """ + Host: 8.8.8.8 (google-public-dns-a.google.com) + OpenPort: 53/tcp + Service: domain (DNS/TCP) + """ + try: + socket.setdefaulttimeout(timeout) + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) + return True + except Exception as ex: + print ex.message + return False + def notify_unreplied(): """Sends email notifications if there are unreplied Communications and `notify_if_unreplied` is set as true.""" @@ -451,6 +574,7 @@ def notify_unreplied(): "sent_or_received": "Received", "reference_doctype": email_account.append_to, "unread_notification_sent": 0, + "email_account":email_account.name, "creation": ("<", datetime.now() - timedelta(seconds = (email_account.unreplied_for_mins or 30) * 60)), "creation": (">", datetime.now() - timedelta(seconds = (email_account.unreplied_for_mins or 30) * 60 * 3)) }): @@ -467,9 +591,13 @@ def notify_unreplied(): def pull(now=False): """Will be called via scheduler, pull emails from all enabled Email accounts.""" + if frappe.cache().get_value("workers:no-internet") == True: + if test_internet(): + frappe.cache().set_value("workers:no-internet", False) + else: + return queued_jobs = get_jobs(site=frappe.local.site, key='job_name')[frappe.local.site] - - for email_account in frappe.get_list("Email Account", filters={"enable_incoming": 1}): + for email_account in frappe.get_list("Email Account",["name", "no_remaining"], filters={"enable_incoming": 1, "awaiting_password": 0}): if now: pull_from_email_account(email_account.name) @@ -478,8 +606,12 @@ def pull(now=False): job_name = 'pull_from_email_account|{0}'.format(email_account.name) if job_name not in queued_jobs: - enqueue(pull_from_email_account, 'short', event='all', job_name=job_name, - email_account=email_account.name) + if email_account.no_remaining == '0': + enqueue(pull_from_email_account, 'short', event='all', job_name=job_name, + email_account=email_account.name) + else: + enqueue(pull_from_email_account, 'long', event='all', job_name=job_name, + email_account=email_account.name) def pull_from_email_account(email_account): '''Runs within a worker process''' diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index 5513c7dc42..549d721e64 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -129,7 +129,7 @@ class TestEmailAccount(unittest.TestCase): # send sent_name = make(subject = "Test", content="test content", - recipients="test_receiver@example.com", sender="test@example.com", + recipients="test_receiver@example.com", sender="test@example.com",doctype="ToDo",name=frappe.get_last_doc("ToDo").name, send_email=True)["name"] sent_mail = email.message_from_string(frappe.get_last_doc("Email Queue").message) @@ -146,8 +146,8 @@ class TestEmailAccount(unittest.TestCase): sent = frappe.get_doc("Communication", sent_name) comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) - self.assertEquals(comm.reference_doctype, sent.doctype) - self.assertEquals(comm.reference_name, sent.name) + self.assertEquals(comm.reference_doctype, sent.reference_doctype) + self.assertEquals(comm.reference_name, sent.reference_name) def test_threading_by_subject(self): frappe.db.sql("""delete from tabCommunication diff --git a/frappe/email/doctype/email_account/test_records.json b/frappe/email/doctype/email_account/test_records.json index 70e8b2f927..fbe7d9c281 100644 --- a/frappe/email/doctype/email_account/test_records.json +++ b/frappe/email/doctype/email_account/test_records.json @@ -3,6 +3,7 @@ "is_default": 1, "is_global": 1, "doctype": "Email Account", + "domain":"example.com", "append_to": "ToDo", "email_account_name": "_Test Email Account 1", "enable_outgoing": 1, @@ -17,6 +18,11 @@ "notify_if_unreplied": 1, "unreplied_for_mins": 20, "send_notification_to": "test_unreplied@example.com", - "pop3_server": "pop.test.example.com" + "pop3_server": "pop.test.example.com", + "no_remaining":"0" + }, + { + "doctype": "ToDo", + "description":"test doctype" } ] diff --git a/frappe/email/doctype/email_domain/__init__.py b/frappe/email/doctype/email_domain/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/email_domain/email_domain.js b/frappe/email/doctype/email_domain/email_domain.js new file mode 100644 index 0000000000..3c3c47ff78 --- /dev/null +++ b/frappe/email/doctype/email_domain/email_domain.js @@ -0,0 +1,16 @@ + +frappe.ui.form.on("Email Domain", { + email_id:function(frm){ + frm.set_value("domain_name",frm.doc.email_id.split("@")[1]) + }, + refresh:function(frm){ + if (frm.doc.email_id){frm.set_value("domain_name",frm.doc.email_id.split("@")[1])} + if (frm.doc.__islocal != 1) { + route = frappe.get_prev_route() + if (frappe.route_titles["return to email_account"]){ + delete frappe.route_titles["return to email_account"]; + frappe.set_route(route); + } + } + } +}) diff --git a/frappe/email/doctype/email_domain/email_domain.json b/frappe/email/doctype/email_domain/email_domain.json new file mode 100644 index 0000000000..b2a2cd69a6 --- /dev/null +++ b/frappe/email/doctype/email_domain/email_domain.json @@ -0,0 +1,443 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:domain_name", + "beta": 0, + "creation": "2016-03-29 10:50:48.848239", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "email_settings", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "domain_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "domain name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "email_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Example Email Address", + "length": 0, + "no_copy": 0, + "options": "Email", + "permlevel": 0, + "precision": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mailbox_settings", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "e.g. pop.gmail.com / imap.gmail.com", + "fieldname": "email_server", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Email Server", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "use_imap", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use IMAP", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "use_ssl", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use SSL", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "depends_on": "", + "description": "Ignore attachments over this size", + "fieldname": "attachment_limit", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Attachment Limit (MB)", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "Append as communication against this DocType (must have fields, \"Status\", \"Subject\")", + "fieldname": "append_to", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Append To", + "length": 0, + "no_copy": 0, + "options": "DocType", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "outgoing_mail_settings", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "e.g. smtp.gmail.com", + "fieldname": "smtp_server", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "SMTP Server", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "use_tls", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use TLS", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "description": "If non standard port (e.g. 587)", + "fieldname": "smtp_port", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Port", + "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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-inbox", + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-12-23 13:31:58.408528", + "modified_by": "Administrator", + "module": "Email", + "name": "Email Domain", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 1, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/email/doctype/email_domain/email_domain.py b/frappe/email/doctype/email_domain/email_domain.py new file mode 100644 index 0000000000..baca02ade9 --- /dev/null +++ b/frappe/email/doctype/email_domain/email_domain.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.utils import validate_email_add ,cint +import imaplib,poplib,smtplib + +class EmailDomain(Document): + def autoname(self): + if self.domain_name: + self.name = self.domain_name + + + def validate(self): + """Validate email id and check POP3/IMAP and SMTP connections is enabled.""" + if self.email_id: + validate_email_add(self.email_id, True) + + if frappe.local.flags.in_patch or frappe.local.flags.in_test: + return + + if not frappe.local.flags.in_install and not frappe.local.flags.in_patch: + try: + if self.use_imap: + if self.use_ssl: + test = imaplib.IMAP4_SSL(self.email_server) + else: + test = imaplib.IMAP4(self.email_server) + + else: + if self.use_ssl: + test = poplib.POP3_SSL(self.email_server) + else: + test = poplib.POP3(self.email_server) + + except Exception: + frappe.throw("Incoming email account not correct") + return None + finally: + try: + if self.use_imap: + test.logout() + else: + test.quit() + except Exception: + pass + try: + if self.use_tls and not self.smtp_port: + self.port = 587 + sess = smtplib.SMTP((self.smtp_server or "").encode('utf-8'), cint(self.smtp_port) or None) + sess.quit() + except Exception as e: + frappe.throw("Outgoing email account not correct") + return None + return + + def on_update(self): + """update all email accounts using this domain""" + for email_account in frappe.get_all("Email Account", + filters={"domain": self.name}): + + try: + email_account = frappe.get_doc("Email Account", + email_account.name) + email_account.set("email_server",self.email_server) + email_account.set("use_imap",self.use_imap) + email_account.set("use_ssl",self.use_ssl) + email_account.set("use_tls",self.use_tls) + email_account.set("attachment_limit",self.attachment_limit) + email_account.set("smtp_server",self.smtp_server) + email_account.set("smtp_port",self.smtp_port) + email_account.save() + except Exception, e: + frappe.msgprint(email_account.name) + frappe.throw(e) + return None + diff --git a/frappe/email/doctype/email_domain/test_email_domain.py b/frappe/email/doctype/email_domain/test_email_domain.py new file mode 100644 index 0000000000..0050b3250a --- /dev/null +++ b/frappe/email/doctype/email_domain/test_email_domain.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Domain') + +class TestDomain(unittest.TestCase): + pass diff --git a/frappe/email/doctype/email_flag_queue/__init__.py b/frappe/email/doctype/email_flag_queue/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/email_flag_queue/email_flag_queue.js b/frappe/email/doctype/email_flag_queue/email_flag_queue.js new file mode 100644 index 0000000000..19c4d4b0c1 --- /dev/null +++ b/frappe/email/doctype/email_flag_queue/email_flag_queue.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Email Flag Queue', { + refresh: function(frm) { + + } +}); diff --git a/frappe/email/doctype/email_flag_queue/email_flag_queue.json b/frappe/email/doctype/email_flag_queue/email_flag_queue.json new file mode 100644 index 0000000000..64932b069d --- /dev/null +++ b/frappe/email/doctype/email_flag_queue/email_flag_queue.json @@ -0,0 +1,144 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-04-20 15:29:39.785172", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "comm_name", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "comm_name", + "length": 0, + "no_copy": 0, + "options": "Communication", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "action", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "action", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "flag", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "flag", + "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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-12-22 10:37:28.738451", + "modified_by": "Administrator", + "module": "Email", + "name": "Email Flag Queue", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/email/doctype/email_flag_queue/email_flag_queue.py b/frappe/email/doctype/email_flag_queue/email_flag_queue.py new file mode 100644 index 0000000000..487ef7db50 --- /dev/null +++ b/frappe/email/doctype/email_flag_queue/email_flag_queue.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, 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 EmailFlagQueue(Document): + pass diff --git a/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py b/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py new file mode 100644 index 0000000000..644a2a8ff7 --- /dev/null +++ b/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Email Flag Queue') + +class TestEmailFlagQueue(unittest.TestCase): + pass diff --git a/frappe/email/doctype/email_queue/email_queue_list.js b/frappe/email/doctype/email_queue/email_queue_list.js index 1249183467..5ea247dbb9 100644 --- a/frappe/email/doctype/email_queue/email_queue_list.js +++ b/frappe/email/doctype/email_queue/email_queue_list.js @@ -2,5 +2,22 @@ frappe.listview_settings['Email Queue'] = { get_indicator: function(doc) { colour = {'Sent': 'green', 'Sending': 'blue', 'Not Sent': 'grey', 'Error': 'red', 'Expired': 'orange'}; return [__(doc.status), colour[doc.status], "status,=," + doc.status]; + }, + refresh: function(doclist){ + if (has_common(user_roles, ["Administrator", "System Manager"])){ + if (cint(frappe.defaults.get_default("hold_queue"))){ + doclist.page.clear_inner_toolbar() + doclist.page.add_inner_button(__("Resume Sending"), function() { + frappe.defaults.set_default("hold_queue", 0); + cur_list.refresh(); + }) + } else { + doclist.page.clear_inner_toolbar() + doclist.page.add_inner_button(__("Suspend Sending"), function() { + frappe.defaults.set_default("hold_queue", 1) + cur_list.refresh(); + }) + } + } } } diff --git a/frappe/email/doctype/unhandled_email/__init__.py b/frappe/email/doctype/unhandled_email/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/unhandled_email/test_unhandled_email.py b/frappe/email/doctype/unhandled_email/test_unhandled_email.py new file mode 100644 index 0000000000..6cabcf6ec2 --- /dev/null +++ b/frappe/email/doctype/unhandled_email/test_unhandled_email.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Unhandled Emails') + +class TestUnhandledEmail(unittest.TestCase): + pass diff --git a/frappe/email/doctype/unhandled_email/unhandled_email.json b/frappe/email/doctype/unhandled_email/unhandled_email.json new file mode 100644 index 0000000000..ab1972bd71 --- /dev/null +++ b/frappe/email/doctype/unhandled_email/unhandled_email.json @@ -0,0 +1,204 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2016-04-14 09:41:45.892975", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "email_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Email Account", + "length": 0, + "no_copy": 0, + "options": "Email Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "uid", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "uid", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "reason", + "fieldtype": "Long Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Reason", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "message_id", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Message-id", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "unique_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Unique id", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "raw", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Raw Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-07-21 00:35:02.752145", + "modified_by": "Administrator", + "module": "Email", + "name": "Unhandled Email", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 1 + } + ], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/frappe/email/doctype/unhandled_email/unhandled_email.py b/frappe/email/doctype/unhandled_email/unhandled_email.py new file mode 100644 index 0000000000..edbb410106 --- /dev/null +++ b/frappe/email/doctype/unhandled_email/unhandled_email.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class UnhandledEmail(Document): + def on_trash(self): + frappe.db.set_value("Email Account",self.email_account,"no_remaining",None) diff --git a/frappe/email/page/__init__.py b/frappe/email/page/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/page/email_inbox/__init__.py b/frappe/email/page/email_inbox/__init__.py new file mode 100644 index 0000000000..4e141cbed1 --- /dev/null +++ b/frappe/email/page/email_inbox/__init__.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from frappe.model.document import Document +from frappe.desk.form.load import get_attachments + + +@frappe.whitelist() +def get_list(email_account,start,page_length): + inbox_list = [] + communications = frappe.db.sql("""select name, sender, sender_full_name, actualdate, recipients, communication_medium as comment_type, subject, status ,reference_doctype,reference_name,timeline_doctype,timeline_name,timeline_label,sent_or_received,uid,message_id, seen,nomatch,has_attachment + from tabCommunication + where email_account = %(email_account)s and deleted = 0 + ORDER BY actualdate DESC + LIMIT %(page_length)s OFFSET %(start)s""",{"email_account":email_account,"start":int(start),"page_length":int(page_length)},as_dict=1) + for c in communications: + comm = {} + + comm["name"] = c.get('name') + comm["reference_doctype"] = c.get('reference_doctype') + comm["reference_name"] = c.get('reference_name') + if c.get('recipients') != None: + comm["recipients"] = c.get('recipients').replace('"',"").strip("<>") + comm["sender"] = c.get('sender') + comm["sender_full_name"] = c.get('sender_full_name') + comm["actualdate"] = c.get('actualdate') + comm["subject"] = c.get('subject') + comm["status"] = c.get('status') + comm["content"] = c.get('content') + comm["timeline_doctype"] = c.get('timeline_doctype') + comm["timeline_name"] = c.get('timeline_name') + comm["timeline_label"] = c.get('timeline_label') + comm["sent_or_received"] = c.get('sent_or_received') + comm["uid"]= c.get('uid') + comm["message_id"]=c.get("message_id") + comm["seen"] = c.get('seen') + comm["nomatch"] =c.get('nomatch') + comm["has_attachment"]=c.get('has_attachment') + inbox_list.append(comm) + return inbox_list + +@frappe.whitelist() +def get_email_content(name): + docinfo = frappe.desk.form.load.get_attachments("Communication",name) + content = frappe.db.get_value("Communication", name,"content") + return docinfo, content + +@frappe.whitelist() +def create_flag_queue(names,action,flag,field): + names = json.loads(names) + class Found(Exception): + pass + + for item in names: + if item["uid"]: + state = frappe.db.get_value("Communication", item["name"], field) + if (action =='+FLAGS' and state ==0) or (action =='-FLAGS' and state ==1): #check states are correct + try: + queue = frappe.db.sql("""select name,action,flag from `tabEmail Flag Queue` + where comm_name = %(name)s""",{"name":item["name"]},as_dict=1) + for q in queue: + if q.flag==flag:#is same email with same flag + if q.action!=action:#to prevent flag local and server states being out of sync + frappe.delete_doc("Email Flag Queue", q.name) + raise Found + + flag_queue = frappe.get_doc({ + "doctype": "Email Flag Queue", + "comm_name": str(item["name"]), + "action":action, + "flag":flag + }) + flag_queue.save(ignore_permissions=True); + except Found: + pass + +@frappe.whitelist() +def setnomatch(name): + frappe.db.set_value("Communication", str(name), "nomatch", 1, update_modified=False) + +@frappe.whitelist() +def update_local_flags(names,field,val): + names = json.loads(names) + for d in names: + frappe.db.set_value("Communication", str(d["name"]), field, val,update_modified=False) + +@frappe.whitelist() +def get_length(email_account): + try: + return frappe.db.sql("""select count(name) + from tabCommunication + where deleted = 0 and email_account= %(email_account)s""",{"email_account":email_account}) + except: + return 0 + +@frappe.whitelist() +def get_accounts(user): + try: + return frappe.db.sql("""select email_account,email_id + from `tabUser Email` + where parent = %(user)s + order by idx""",{"user":user},as_dict=1) + except: + return + +# for the selection/deletion of multiple items +def set_multiple_status(names, status): + names = json.loads(names) + for name in names: + set_status(name, status) + +def set_status(name, status): + st = frappe.get_doc("Issue", name) + st.status = status + st.save() diff --git a/frappe/email/page/email_inbox/email_inbox.js b/frappe/email/page/email_inbox/email_inbox.js new file mode 100644 index 0000000000..7a6ef5711b --- /dev/null +++ b/frappe/email/page/email_inbox/email_inbox.js @@ -0,0 +1,636 @@ +frappe.pages['email_inbox'].on_page_load = function(wrapper) { + frappe.ui.make_app_page({ + parent: wrapper, + title: 'Email Inbox', + icon: 'fa fa-inbox', + single_column: false + }); + + frappe.model.with_doctype('Communication', function() { + wrapper.Inbox = new frappe.Inbox({ + method: 'frappe.desk.reportview.get', + wrapper: wrapper, + page: wrapper.page, + no_loading: true + }); + }); +}; + +frappe.pages['email_inbox'].refresh = function(wrapper) { + if (wrapper.inbox) { + wrapper.Inbox.refresh() + } +}; + +frappe.Inbox = frappe.ui.Listing.extend({ + init: function(opts) { + $.extend(this, opts); + wrap = this; + this.wrapper = opts.wrapper; + this.filters = {}; + this.page_length = 20; + this.start = 0; + this.cur_page = 1; + this.no_result_message = 'No Emails to Display'; + + this.render_sidemenu(); + if (this.account) { + var me = this; + // setup listing + me.make({ + doctype: 'Communication', + page: me.page, + method: 'frappe.desk.reportview.get', + get_args: me.get_args, + parent: me.page.main, + start: 0, + show_filters: true + }); + this.filter_list.add_filter("Communication", "deleted", "=", "No"); + this.render_headers(); + this.render_footer(); + this.run(); + this.render_buttons(); + this.init_select_all(); + var me = this; + frappe.realtime.on("new_email", function(data) { + for(var i =0; i'+list["message"][i]["email_id"]+' '; + me.accounts.push({name:list["message"][i]["email_account"],email:list["message"][i]["email_id"]}) + } + me.allaccounts = $.map(me.accounts,function(v){return v.name}).join(","); + buttons += ''; + buttons += rows; + buttons += ''; + me.account = me.allaccounts; + me.default_filters=[ + ["Communication", "communication_type", "=", "Communication"], + ["Communication", "email_account", "in", me.account], + ["Communication", "sent_or_received", "=", "Received"]] + + me.page.sidebar.empty().append(buttons); + $(".inbox-select").on("click",function(btn){ + me.account = $(btn.currentTarget).find(".inbox-item").data("account"); + $(me.page.sidebar).find(".list-row").removeClass("list-row-head").css("font-weight","normal"); + $(btn.currentTarget).closest(".list-row").addClass("list-row-head").css("font-weight","bold"); + me.cur_page = 1; + $(me.page.main).find(".list-select-all,.list-delete").prop("checked",false); + me.toggle_actions(); + + if(me.account=="Sent"){ + me.filter_list.default_filters=[ + ["Communication", "communication_type", "=", "Communication"], + ["Communication", "sent_or_received", "=", "Sent"]] + }else { + me.filter_list.default_filters = [ + ["Communication", "communication_type", "=", "Communication"], + ["Communication", "email_account", "in", me.account], + ["Communication", "sent_or_received", "=", "Received"]]; + } + me.filter_list.clear_filters(); + me.filter_list.add_filter("Communication", "deleted", "=", "No"); + if (me.filter_list.reload_stats){me.filter_list.reload_stats()} + me.refresh(); + }); + } + } + }) + }, + get_args: function(){ + var args = { + doctype: this.doctype, + fields:["name", "sender", "sender_full_name", "communication_date", "recipients", "cc","communication_medium", + "subject", "status" ,"reference_doctype", "reference_name", "timeline_doctype", "timeline_name", + "timeline_label", "sent_or_received", "uid", "message_id", "seen", "nomatch", "has_attachment", "timeline_hide"], + filters: this.filter_list.get_filters(), + order_by: 'communication_date desc', + save_list_settings: false + }; + + args.filters = args.filters.concat(this.filter_list.default_filters) + + return args; + }, + render_list:function(data){ + var me = this + $(me.wrapper).find(".result-list").html(""); + for (var i = 0; i < data.length; i++) + { + this.prepare_row(data[i]); + $(frappe.render_template("inbox_list", {data: data[i]})).data("data", data[i]).appendTo($(me.wrapper).find(".result-list")) + } + //click action + $(me.wrapper).find(".result-list").find(".list-row").click(function (btn) { + if ($(btn.target).hasClass("noclick")) { + return + } + var row = $(btn.target).closest(".list-row").data("data"); + if($(btn.target).hasClass("relink-link")){ + me.relink(row); + return + } + if(me.account!="Sent") { + if ($(btn.target).hasClass("company-link")) { + me.company_select(row, true); + return + } + } + me.email_open(row); + }); + }, + prepare_row:function(row){ + row.hascompany =(row.customer || row.supplier) ? true : false; + row.seen = this.account!="Sent" ? row.seen : 1; + }, + render_footer:function(){ + var me = this; + me.footer = $(me.wrapper).append(frappe.render_template("inbox_footer","")).find(".foot-con"); + frappe.require('assets/frappe/js/lib/bootstrap-paginator.min.js',function(){ + me.footer.bootstrapPaginator({ + currentPage: 1, + totalPages: 10, + bootstrapMajorVersion:3, + onPageClicked: function(e,originalEvent,type,page){ + me.cur_page = page; + $('.footer-numbers').html('showing: ' + (me.cur_page - 1) * me.page_length + ' to ' + ( + (me.data_length > (me.cur_page * me.page_length))?(me.cur_page * me.page_length):me.data_length) + ' of ' + me.data_length); + me.run(true,true); + } + }); + }); + $(me.wrapper).find('.list-paging-area').addClass('hide'); + }, + update_footer:function(){ + var me = this; + //default filter used for filters + var filters = me.filter_list.get_filters(); + if (me.filter_list.default_filters){filters = filters.concat(me.filter_list.default_filters)} + return frappe.call({ + method: me.method || 'frappe.desk.query_builder.runquery', + type: "GET", + freeze: (me.freeze != undefined ? me.freeze : true), + args: { + doctype: me.doctype, + fields: ["count(*) as number"], + filters: filters, + save_list_settings: false + }, + callback: function (r) { + r.values = me.get_values_from_response(r.message); + me.data_length = r.values[0]["number"] + if (me.data_length != 0) { + me.footer.show(); + me.last_page = Math.ceil(me.data_length / me.page_length); + frappe.require('assets/frappe/js/lib/bootstrap-paginator.min.js',function(){ + me.footer.bootstrapPaginator({currentPage: 1, totalPages: me.last_page}) + }); + } else { + me.footer.hide(); + } + $('.footer-numbers').html('showing: ' + (me.cur_page - 1) * me.page_length + ' to ' + ( + (me.data_length > (me.cur_page * me.page_length)) ? (me.cur_page * me.page_length) : me.data_length) + ' of ' + me.data_length); + }, + no_spinner: this.no_loading + }); + }, + company_select:function(row,nomatch) + { + var me = this; + var fields = [{ + "fieldtype": "Heading", + "label": __("Create new Contact to Match Email Address"), + "fieldname": "Option1" + }, + { + "fieldtype": "Button", + "label": __("Create/Add new Contact"), + "fieldname":"newcontact", + "description": __('Create new Contact for a Customer, Supplier, User or Organisation to Match "') + row.sender + __('" Against') + } + + ]; + if (!nomatch) { + fields.push({ + "fieldtype": "Heading", + "label": __("Do not Match"), + "fieldname": "Option3" + }); + fields.push({ + "fieldtype": "Button", + "label": __("Do not Match"), + "fieldname":"nomatch" + }) + } + var d = new frappe.ui.Dialog ({ + title: __("Match Emails to a Company"), + fields: fields + }); + d.get_input("newcontact").on("click", function (frm) { + d.hide(); + delete frappe.route_titles["update_contact"]; + frappe.route_titles["create_contact"] = 1; + var name_split = row.sender_full_name?row.sender_full_name.split(' '):["",""]; + row.nomatch = 1; + + frappe.route_options = { + "email_id": row.sender, + "first_name": name_split[0], + "last_name":name_split[name_split.length-1], + "status": "Passive" + }; + frappe.model.with_doctype("Contact", function() { + var doc = frappe.model.get_new_doc("Contact"); + frappe.set_route("Form", "Contact", doc.name); + }) + }); + if (!nomatch) { + d.get_input("nomatch").on("click", function (frm) { + d.hide(); + frappe.call({ + method: 'frappe.email.page.email_inbox.setnomatch', + args: { + name: row.name + } + }); + row.nomatch = 1; + if (!nomatch) { + me.email_open(row) + } + }); + } + d.show(); + }, + email_open:function(row) + { + var me = this; + me.actions_opened = false; + if(me.open_email == row.name){ + return + } + me.open_email = row.name + + //mark email as read + if(me.account!="Sent") { + this.mark_read(row); + } + //start of open email + + var emailitem = new frappe.ui.Dialog ({ + title: __(row.subject), + fields: [{ + "fieldtype": "HTML", + "fieldname": "email" + }] + }); + //prompt for match + if (!row.timeline_label && !row.nomatch && me.account!="Sent") { + setTimeout(function () { + if (frappe.ui.open_dialogs.indexOf(emailitem) != -1 && !me.actions_opened) { + me.company_select(row) + }}, 4000); + } + + var c = me.prepare_email(row); + emailitem.fields_dict.email.$wrapper.html(frappe.render_template("inbox_email", {data:c})); + $(emailitem.$wrapper).find(".reply").find("a").attr("target", "_blank"); + + //Action buttons + $(emailitem.$wrapper).find(".text-right").prepend(frappe.render_template("inbox_email_actions",{data:row})).on("click", function () { + me.actions_opened = true; + }); + $(emailitem.$wrapper).find(".relink-link").on("click", function () { + me.relink(row); }); + $(emailitem.$wrapper).find(".delete-link").on("click", function () { + me.delete_email({n:row.name, u:row.uid}); + emailitem.hide() + }); + + $(emailitem.$wrapper).find(".company-link").on("click", function () { + me.company_select(row, true)}); + me.add_reply_btn_event(emailitem, c); + + //adjust sizing + $(".modal-dialog").addClass("modal-lg"); + $(emailitem.$wrapper).find(".modal-title").parent().removeClass("col-xs-7").addClass("col-xs-7 col-sm-8 col-md-9"); + $(emailitem.$wrapper).find(".text-right").parent().removeClass("col-xs-5").addClass("col-xs-5 col-sm-4 col-md-3"); + + //setup close + emailitem.onhide = function() { + me.open_email = null + } + + emailitem.show(); + }, + add_reply_btn_event: function (emailitem, c) { + var me = this; +//reply + $(emailitem.$wrapper).find(".reply-link").on("click", function () { + var sender = ""; + for (var i=0;i")[0]; + c.comment = frappe.utils.strip_original_content(c.comment); + c.comment = frappe.dom.remove_script_and_style(c.comment); + + c.original_comment = c.comment; + c.comment = frappe.utils.toggle_blockquote(c.comment); + } + + + if (!frappe.utils.is_html(c.comment)) { + c.comment_html = frappe.markdown(__(c.comment)); + } else { + c.comment_html = c.comment; + c.comment_html = frappe.utils.strip_whitespace(c.comment_html); + c.comment_html = c.comment_html.replace(/</g,"<").replace(/>/g,">") + } + + + + // bold @mentions + if (c.comment_type === "Comment") { + c.comment_html = c.comment_html.replace(/(^|\W)(@\w+)/g, "$1$2"); + } + + return c + }, + init_select_all: function () { + var me = this; + + $(".list-select-all").on("click", function () { + $(me.wrapper).find('.list-delete').prop("checked", $(this).prop("checked")); + me.toggle_actions(); + }); + + $(me.wrapper).on("click", ".list-delete", function (event) { + me.toggle_actions(); + + // multi-select using shift key + var $this = $(this); + if (event.shiftKey && $this.prop("checked")) { + var $end_row = $this.parents(".list-row"); + var $start_row = $end_row.prevAll(".list-row") + .find(".list-delete:checked").last().parents(".list-row"); + if ($start_row) { + $start_row.nextUntil($end_row).find(".list-delete").prop("checked", true); + } + } + }); + + // after delete, hide delete button + me.toggle_actions(); + }, + render_buttons: function(){ + var me = this; + + me.page.add_action_item("Delete", function(){me.delete_email()}); + me.page.add_action_item("Mark as UnRead", function(){me.mark_unread()}); + me.page.add_action_item("Mark as Read", function(){me.mark_read()}); + + me.page.set_primary_action("New Email", function(){ + var sender = ""; + for (var i=0;i +
+ + + {% if(data.attachments && data.attachments.length) { %} +
+ {% $.each(data.attachments, function(i, a) { %} + + {% }); %} +
+ {% } %} + +
+
+ {%= data.comment_html %} +
+
+
+ diff --git a/frappe/email/page/email_inbox/inbox_email_actions.html b/frappe/email/page/email_inbox/inbox_email_actions.html new file mode 100644 index 0000000000..5247f7709b --- /dev/null +++ b/frappe/email/page/email_inbox/inbox_email_actions.html @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/frappe/email/page/email_inbox/inbox_filter.html b/frappe/email/page/email_inbox/inbox_filter.html new file mode 100644 index 0000000000..e31fa10359 --- /dev/null +++ b/frappe/email/page/email_inbox/inbox_filter.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/frappe/email/page/email_inbox/inbox_footer.html b/frappe/email/page/email_inbox/inbox_footer.html new file mode 100644 index 0000000000..74e189e329 --- /dev/null +++ b/frappe/email/page/email_inbox/inbox_footer.html @@ -0,0 +1,9 @@ +
+
+
\ No newline at end of file diff --git a/frappe/email/page/email_inbox/inbox_headers.html b/frappe/email/page/email_inbox/inbox_headers.html new file mode 100644 index 0000000000..55d16d8f8c --- /dev/null +++ b/frappe/email/page/email_inbox/inbox_headers.html @@ -0,0 +1,27 @@ +
+
+
+
+
+ + Sender +
+
+ Subject +
+ + + + +
+
+
+
diff --git a/frappe/email/page/email_inbox/inbox_list.html b/frappe/email/page/email_inbox/inbox_list.html new file mode 100644 index 0000000000..b438f7dbfb --- /dev/null +++ b/frappe/email/page/email_inbox/inbox_list.html @@ -0,0 +1,46 @@ +
+
+
+
+
+ + + {% if (data.sender_full_name){var sender = data.sender_full_name} else {var sender = data.sender} %} + {%= sender %} + +
+ + + + + + +
+
+
+
\ No newline at end of file diff --git a/frappe/email/queue.py b/frappe/email/queue.py index afbb656dc0..01e7d8b409 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -10,7 +10,7 @@ from frappe.email.smtp import SMTPServer, get_outgoing_email_account from frappe.email.email_body import get_email, get_formatted_html from frappe.utils.verified_command import get_signed_params, verify_request from html2text import html2text -from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails, cstr +from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails, cstr, cint from rq.timeouts import JobTimeoutException from frappe.utils.scheduler import log @@ -19,7 +19,7 @@ class EmailLimitCrossedError(frappe.ValidationError): pass def send(recipients=None, sender=None, subject=None, message=None, reference_doctype=None, reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, - expose_recipients=None, send_priority=1, communication=None, now=False): + expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None): """Add email to sending queue (Email Queue) :param recipients: List of recipients. @@ -85,7 +85,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc # add to queue email_queue = add(recipients, sender, subject, email_content, email_text_context, reference_doctype, reference_name, attachments, reply_to, cc, message_id, in_reply_to, send_after, send_priority, email_account=email_account, communication=communication, - unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, expose_recipients=expose_recipients) + unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, expose_recipients=expose_recipients, read_receipt=read_receipt) if now: send_one(email_queue.name, now=True) @@ -93,7 +93,7 @@ def send(recipients=None, sender=None, subject=None, message=None, reference_doc def add(recipients, sender, subject, formatted, text_content=None, reference_doctype=None, reference_name=None, attachments=None, reply_to=None, cc=[], message_id=None, in_reply_to=None, send_after=None, send_priority=1, email_account=None, - communication=None, unsubscribe_method=None, unsubscribe_params=None, expose_recipients=None): + communication=None, unsubscribe_method=None, unsubscribe_params=None, expose_recipients=None, read_receipt=None): """Add to Email Queue""" e = frappe.new_doc('Email Queue') e.priority = send_priority @@ -104,6 +104,8 @@ def add(recipients, sender, subject, formatted, text_content=None, cc=cc, email_account=email_account, expose_recipients=expose_recipients) mail.set_message_id(message_id) + if read_receipt: + mail.msg_root["Disposition-Notification-To"] = sender if in_reply_to: mail.set_in_reply_to(in_reply_to) @@ -240,6 +242,9 @@ def flush(from_test=False): for i in xrange(cache.llen('cache_email_queue')): email = cache.lpop('cache_email_queue') + if cint(frappe.defaults.get_defaults().get("hold_queue"))==1: + break + if email: send_one(email, smtpserver, auto_commit, from_test=from_test) @@ -272,6 +277,8 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals if frappe.are_emails_muted(): frappe.msgprint(_("Emails are muted")) return + if cint(frappe.defaults.get_defaults().get("hold_queue"))==1 : + return if email.status not in ('Not Sent','Partially Sent') : # rollback to release lock and return diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 7bceaab1be..f6898de432 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -2,7 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals -import time, _socket, poplib, imaplib, email, email.utils, datetime, chardet, re +import time, _socket, poplib, imaplib, email, email.utils, datetime, chardet, re, hashlib from email_reply_parser import EmailReplyParser from email.header import decode_header import frappe @@ -11,6 +11,9 @@ from frappe.utils import (extract_email_id, convert_utc_to_user_timezone, now, cint, cstr, strip, markdown) from frappe.utils.scheduler import log from frappe.utils.file_manager import get_random_filename, save_file, MaxFileSizeReachedError +from email_reply_parser import EmailReplyParser +from email.header import decode_header +from frappe.utils.file_manager import get_random_filename class EmailSizeExceededError(frappe.ValidationError): pass class EmailTimeoutError(frappe.ValidationError): pass @@ -46,6 +49,7 @@ class EmailServer: try: if cint(self.settings.use_ssl): self.imap = Timed_IMAP4_SSL(self.settings.host, timeout=frappe.conf.get("pop_timeout")) + #self.imap = imaplib.IMAP4_SSL(self.settings.host) else: self.imap = Timed_IMAP4(self.settings.host, timeout=frappe.conf.get("pop_timeout")) self.imap.login(self.settings.username, self.settings.password) @@ -98,42 +102,56 @@ class EmailServer: frappe.db.commit() - if not self.connect(): - return [] - try: # track if errors arised self.errors = False self.latest_messages = [] + if cint(self.settings.use_imap): + uid_validity = self.get_status() + else: + email_list = self.get_new_mails() - email_list = self.get_new_mails() - num = num_copy = len(email_list) - - # WARNING: Hard coded max no. of messages to be popped - if num > 20: num = 20 # size limits self.total_size = 0 self.max_email_size = cint(frappe.local.conf.get("max_email_size")) self.max_total_size = 5 * self.max_email_size + if cint(self.settings.use_imap): + #try: + if self.check_uid_validity(uid_validity): + email_list = self.get_new_mails() + if email_list: + self.get_imap_messages(email_list) + self.sync_flags() + self.get_seen() + self.push_deleted() - for i, message_meta in enumerate(email_list): - # do not pull more than NUM emails - if (i+1) > num: - break + else: + pass - try: - self.retrieve_message(message_meta, i+1) - except (TotalSizeExceededError, EmailTimeoutError, LoginLimitExceeded): - break + else: + num = num_copy = len(email_list) - # WARNING: Mark as read - message number 101 onwards from the pop list - # This is to avoid having too many messages entering the system - num = num_copy - if not cint(self.settings.use_imap): - if num > 100 and not self.errors: - for m in xrange(101, num+1): - self.pop.dele(m) + # WARNING: Hard coded max no. of messages to be popped + if num > 20: num = 20 #20 + + for i, message_meta in enumerate(email_list): + # do not pull more than NUM emails + if (i+1) > num: + break + + try: + self.retrieve_message(message_meta, i+1) + except (TotalSizeExceededError, EmailTimeoutError, LoginLimitExceeded): + break + + # WARNING: Mark as read - message number 101 onwards from the pop list + # This is to avoid having too many messages entering the system + num = num_copy + if not cint(self.settings.use_imap): + if num > 100 and not self.errors: + for m in xrange(101, num+1): + self.pop.dele(m) except Exception, e: if self.has_login_limit_exceeded(e): @@ -151,17 +169,316 @@ class EmailServer: return self.latest_messages + def get_status(self): + passed, status = self.imap.status("Inbox", "(UIDNEXT UIDVALIDITY)") + match = re.search(r"(?<=UIDVALIDITY )[0-9]*", status[0], re.U | re.I) + if match: + uid_validity = match.group(0) + match = re.search(r"(?<=UIDNEXT )[0-9]*", status[0], re.U | re.I) + if match: + uidnext = match.group(0) + frappe.db.sql("update `tabEmail Account` set uidnext = %s where name = %s",(uidnext, self.settings.email_account), auto_commit=1) + self.settings.newuidnext = uidnext + return uid_validity + def get_new_mails(self): """Return list of new mails""" if cint(self.settings.use_imap): self.imap.select("Inbox") - response, message = self.imap.uid('search', None, "UNSEEN") + if self.settings.no_remaining == '0' and self.settings.uidnext: + if self.settings.uidnext == self.settings.newuidnext: + return False + else: + #request all messages between last uidnext and new + return True + else: + response, message = self.imap.uid('search', None, "ALL") email_list = message[0].split() else: email_list = self.pop.list()[1] return email_list + def check_uid_validity(self, uid_validity): + if self.settings.uid_validity: + if self.settings.uid_validity == uid_validity: + return True + else: + #validity changed + self.settings.no_remaining = None + self.rebuild_uid(uid_validity) + return True + + else:#if email account settings is blank + uid_list = frappe.db.sql("""select uid + from tabCommunication + where email_account = %(email_account)s and uid is not Null + order by uid + """,{"email_account":self.settings.email_account}, as_list=1) + new_uid_list = [] + for i in uid_list: + new_uid_list.append(i[0]) + + if new_uid_list:#if email account + self.rebuild_uid(uid_validity) + return True + else:# if no uid and no emails with uid + frappe.db.set_value("Email Account", self.settings.email_account, "uid_validity", uid_validity) + frappe.db.commit() + return True + + def rebuild_uid(self,uid_validity): + uid_list = frappe.db.sql("""select name,uid ,unique_id + from `tabCommunication` + where email_account = %(email_account)s and unique_id is not Null and sent_or_received = 'Received' + #order by uid + """, {"email_account": self.settings.email_account}, as_dict=1) + + unhandled_uid_list = frappe.db.sql("""select name,uid ,unique_id + from `tabUnhandled Email` + where email_account = %(email_account)s and unique_id is not Null + #order by uid + """, {"email_account": self.settings.email_account}, as_dict=1) + + + message_list = [] + #get message-id's to link new uid's to + import email + self.imap.select("Inbox") + #messages = self.imap.uid('fetch', "1:*", '(BODY.PEEK[HEADER.FIELDS (FROM TO ENVELOPE-TO DATE RECEIVED)])') + messages = self.imap.uid('fetch', "1:*", '(BODY.PEEK[HEADER])') + for i, item in enumerate(messages[1]): + if isinstance(item, tuple): + # check for uid appended to the end + uid = re.search(r'UID [0-9]*', messages[1][i + 1], re.U | re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + + # check for uid at start + if not uid: + # for m in item: + uid = re.search(r'UID [0-9]*', messages[1][i][0], re.U | re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + continue + mail = email.message_from_string(item[1]) + unique_id = get_unique_id(mail) + message_list.append([uid, unique_id]) + # clear out + frappe.db.sql("""update `tabCommunication` + set uid = NULL + where email_account = %(email_account)s + """, {"email_account": self.settings.email_account}) + frappe.db.sql("""update `tabUnhandled Email` + set uid = NULL + where email_account = %(email_account)s + """, {"email_account": self.settings.email_account}) + + # write new uid + new_uid = [] + for old in uid_list: + for new in message_list: + if old["unique_id"] == new[1]: + frappe.db.sql("""update `tabCommunication` + set uid = %(uid)s + where name = %(name)s + """, {"name": old["name"], + "uid": new[0]}) + break + for old in unhandled_uid_list: + for new in message_list: + if old["unique_id"] == new[1]: + frappe.db.sql("""update `tabUnhandled Email` + set uid = %(uid)s + where name = %(name)s + """, {"name": old["name"], + "uid": new[0]}) + break + + frappe.db.set_value("Email Account", self.settings.email_account, "uid_validity", uid_validity) + frappe.db.set_value("Email Account", self.settings.email_account, "no_remaining", None) + frappe.db.commit() + + + + def get_imap_messages(self,email_list): + if self.settings.no_remaining == '0' and self.settings.uidnext: + download_list = range(int(self.settings.uidnext), int(self.settings.newuidnext)) + else: + #compare stored uid to new uid list to dl any missing messages + uid_list = frappe.db.sql("""select uid + from tabCommunication + where email_account = %(email_account)s and uid is not Null + order by uid + """,{"email_account":self.settings.email_account},as_list=1) + uid_list = uid_list + (frappe.db.sql("""select uid + from `tabUnhandled Email` + where email_account = %(email_account)s and uid is not Null + order by uid + """,{"email_account":self.settings.email_account},as_list=1)) + new_uid_list = [] + for i in uid_list: + new_uid_list.append(i[0]) + + download_list = [] + for new in email_list: + if new not in new_uid_list: + download_list.append(cint(new)) + + from itertools import count, groupby + num = 50 + + # set number of email remaining to be synced + dl_length = len(download_list) + + lcount =1 + while len(download_list)>0: + # trim list to specified num emails to dl at a time + + dlength = len(download_list) + cur_download_list = download_list[dlength - num:dlength] + if cur_download_list: + download_list = download_list[:dlength - num] + + if lcount>=4: + download_list = [] + + # compress download list into ranges + G=(list(x) for _,x in groupby(cur_download_list, lambda x,c=count(): next(c)-x)) + message_meta = ",".join(":".join(map(str,(g[0],g[-1])[:len(g)])) for g in G) + + messages =[] + + try: + messages = self.imap.uid('fetch', message_meta, '(BODY.PEEK[])') + + except (TotalSizeExceededError, EmailTimeoutError), e: + print("timeout or size exceed") + pass + except (imaplib.IMAP4.error),e: + + print (e) + pass + + if messages and messages[0]=='OK': + for i, item in enumerate(messages[1]): + if isinstance(item, tuple): + #check for uid appended to the end + uid = re.search(r'UID [0-9]*', messages[1][i + 1], re.U|re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + + + #check for uid at start + if not uid: + #for m in item: + uid = re.search(r'UID [0-9]*', messages[1][i][0], re.U|re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + continue + + + if uid: + self.latest_messages.append([item[1],uid,1])#message,uid,seen + + frappe.db.sql("update `tabEmail Account` set no_remaining = %s where name = %s", + (dl_length-len(self.latest_messages), self.settings.email_account), auto_commit=1) + lcount = lcount +1 + + def sync_flags(self): + #get flags from email flag queue + join them to the matching email account and uid + queue = frappe.db.sql("""select que.name,comm.uid,que.action,que.flag + from tabCommunication as comm,`tabEmail Flag Queue` as que + where comm.name = que.comm_name and comm.uid is not null and comm.email_account=%(email_account)s""", + {"email_account":self.settings.email_account}, as_dict=1) + #loop though flags + + for item in queue: + try: + self.imap.uid('STORE', item.uid, item.action, item.flag) + #delete flag matching email account + frappe.delete_doc("Email Flag Queue", item["name"]) + except Exception,e: + #need to do + pass + + def get_seen(self): + comm_list = frappe.db.sql("""select name,uid,seen from `tabCommunication` + where email_account = %(email_account)s and uid is not null""", + {"email_account":self.settings.email_account}, as_dict=1) + + try: + #response, messages = self.imap.uid('fetch', '1:*', '(FLAGS)') + response, seen_list = self.imap.uid('search', None, "SEEN") + response, unseen_list = self.imap.uid('search', None, "UNSEEN") + except Exception,e: + print("failed get seen sync download") + return + unseen_list = unseen_list[0].split() + for unseen in unseen_list: + for msg in self.latest_messages: + if unseen == msg[1]: + msg[2] = 0 + + for comm in comm_list: + if comm.uid == unseen: + if comm.seen: + frappe.db.sql("update `tabCommunication` set seen=%s where name = %s",(0, comm.name)) + comm_list.remove(comm) + break + seen_list = seen_list[0].split() + for seen in seen_list: + for msg in self.latest_messages: + if seen == msg[1]: + msg[2] = 1 + + for comm in comm_list: + if comm.uid == seen: + if not comm.seen: + frappe.db.sql("update `tabCommunication` set seen=%s where name = %s", (1, comm.name)) + comm_list.remove(comm) + break + ''' + for item in messages: + uid = re.search(r'UID [0-9]*', item, re.U | re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + + # flag = re.search(r"(?<=FLAGS \()(.*?)(?=\))", item, re.U | re.I) + flag = re.search(r"\\Seen", item, re.U | re.I) + + for msg in self.latest_messages: + if uid == msg[1]: + if flag: + msg[2]=0 + + for comm in comm_list: + if comm.uid==uid: + if flag: + if not comm.email_seen: + frappe.db.set_value('Communication',comm.name,'email_seen','1',update_modified=False) + else: + if comm.email_seen: + frappe.db.set_value('Communication', comm.name, 'email_seen', '0', update_modified=False) + comm_list.remove(comm) + break + ''' + frappe.db.commit() + + + def push_deleted(self): + pass + def retrieve_message(self, message_meta, msg_num=None): incoming_mail = None try: @@ -194,13 +511,15 @@ class EmailServer: self.pop.dele(msg_num) else: # mark as seen - self.imap.uid('STORE', message_meta, '+FLAGS', '(\\SEEN)') + #self.imap.uid('STORE', message_meta, '+FLAGS', '(\\SEEN)') + pass else: if not cint(self.settings.use_imap): self.pop.dele(msg_num) else: # mark as seen - self.imap.uid('STORE', message_meta, '+FLAGS', '(\\SEEN)') + #self.imap.uid('STORE', message_meta, '+FLAGS', '(\\SEEN)') + pass def has_login_limit_exceeded(self, e): return "-ERR Exceeded the login limit" in strip(cstr(e.message)) @@ -245,6 +564,23 @@ class EmailServer: return error_msg + +def get_unique_id(mail): + hash = hashlib.sha1() + # loop though headers to make unique id looping used to resolve encoding issue of adding together + for h in mail._headers: + if h[0] != 'Content-Type': # skip variable boundaries + try: + temp = decode_header(h[1]) + decoded = ''.join( + [d[0].decode(d[1]).encode('ascii', 'ignore') if d[1] is not None else d[0] for d in temp]) + cleaned = re.sub(r"\s+", u"", decoded, + flags=re.UNICODE) # gmail fix as returns different whitespace if download only headers + hash.update(cleaned) + except: + pass + return hash.hexdigest() + class Email: """Wrapper for an email.""" def __init__(self, content): @@ -264,10 +600,52 @@ class Email: self.set_from() self.message_id = (self.mail.get('Message-ID') or "").strip(" <>") + + self.unique_id = get_unique_id(self.mail) + + # gmail mailing-list compatibility + # use X-Original-Sender if available, as gmail sometimes modifies the 'From' + # _from_email = self.mail.get("X-Original-From") or self.mail["From"] + # + # self.from_email = extract_email_id(_from_email) + # if self.from_email: + # self.from_email = self.from_email.lower() + # + # #self.from_real_name = email.utils.parseaddr(_from_email)[0] + # + # _from_real_name = decode_header(email.utils.parseaddr(_from_email)[0]) + # self.from_real_name = decode_header(email.utils.parseaddr(_from_email)[0])[0][0] or "" + # + # try: + # if _from_real_name[0][1]: + # self.from_real_name = self.from_real_name.decode(_from_real_name[0][1]) + # else: + # # assume that the encoding is utf-8 + # self.from_real_name = self.from_real_name.decode("utf-8") + # except UnicodeDecodeError,e: + # print e + # pass + + #self.from_real_name = email.Header.decode_header(email.utils.parseaddr(_from_email)[0])[0][0] + self.To = self.mail.get("To") + if self.To: + to = u"" + for name, encoding in decode_header(self.To): + if encoding: + to += name.decode(encoding) + else: + to += name + self.To = to.lower() + self.CC = self.mail.get("CC") + if self.CC: + self.CC = self.CC.lower() if self.mail["Date"]: - utc = email.utils.mktime_tz(email.utils.parsedate_tz(self.mail["Date"])) - utc_dt = datetime.datetime.utcfromtimestamp(utc) - self.date = convert_utc_to_user_timezone(utc_dt).strftime('%Y-%m-%d %H:%M:%S') + try: + utc = email.utils.mktime_tz(email.utils.parsedate_tz(self.mail["Date"])) + utc_dt = datetime.datetime.utcfromtimestamp(utc) + self.date = convert_utc_to_user_timezone(utc_dt).strftime('%Y-%m-%d %H:%M:%S') + except: + self.date = now() else: self.date = now() @@ -278,13 +656,32 @@ class Email: def set_subject(self): """Parse and decode `Subject` header.""" - _subject = decode_header(self.mail.get("Subject", "No Subject")) - self.subject = _subject[0][0] or "" - if _subject[0][1]: - self.subject = self.subject.decode(_subject[0][1]) - else: - # assume that the encoding is utf-8 - self.subject = self.subject.decode("utf-8")[:140] + from email.errors import HeaderParseError + try: + _subject = decode_header(self.mail.get("Subject", "No Subject")) + self.subject = _subject[0][0] or "" + + if _subject[0][1]: + self.subject = self.subject.decode(_subject[0][1]) + else: + # assume that the encoding is utf-8 + self.subject = self.subject.decode("utf-8")[:140] + except (UnicodeDecodeError, HeaderParseError): + #try: + # self.subject = self.subject.decode("gb18030") + #except UnicodeDecodeError: + self.subject = u'Error Decoding Subject' + #if self.subject and len(self.subject)>140: + # self.subject = self.subject[:135] + import re + + emoji_pattern = re.compile("[" + u"\U0001F600-\U0001F64F" # emoticons + u"\U0001F300-\U0001F5FF" # symbols & pictographs + u"\U0001F680-\U0001F6FF" # transport & map symbols + u"\U0001F1E0-\U0001F1FF" # flags (iOS) + "]+", flags=re.UNICODE) + self.subject = emoji_pattern.sub(r'', self.subject) if not self.subject: self.subject = "No Subject" @@ -294,14 +691,27 @@ class Email: # use X-Original-Sender if available, as gmail sometimes modifies the 'From' _from_email = self.mail.get("X-Original-From") or self.mail["From"] _from_email, encoding = decode_header(_from_email)[0] + _reply_to, _reply_to_encoding = decode_header(self.mail.get("Reply-To"))[0] if encoding: _from_email = _from_email.decode(encoding) else: _from_email = _from_email.decode('utf-8') + + if _reply_to_encoding: + _reply_to = _from_email.decode(encoding) + else: + _reply_to = _from_email.decode('utf-8') - self.from_email = extract_email_id(_from_email) - self.from_real_name = email.utils.parseaddr(_from_email)[0] + if _reply_to and not frappe.db.get_value('Email Account', {"email_id":_reply_to}, 'email_id'): + self.from_email = extract_email_id(_reply_to) + else: + self.from_email = extract_email_id(_from_email) + + if self.from_email: + self.from_email = self.from_email.lower() + + self.from_real_name = email.utils.parseaddr(_from_email)[0] if "@" in _from_email else _from_email def set_content_and_type(self): self.content, self.content_type = '[Blank Email]', 'text/plain' diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index ae01396150..8d96a3b1a1 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -608,7 +608,7 @@ class BaseDocument(object): if not value or not isinstance(value, basestring): continue - elif ("<" not in value and ">" not in value): + elif (u"<" not in value and u">" not in value): # doesn't look like html so no need continue diff --git a/frappe/patches.txt b/frappe/patches.txt index 2cb28fa766..f686c4ea31 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -22,6 +22,7 @@ execute:frappe.reload_doc('core', 'doctype', 'patch_log') #2016-10-31 execute:frappe.reload_doctype("File") # 2015-10-19 execute:frappe.reload_doc('core', 'doctype', 'error_snapshot') execute:frappe.clear_cache() +frappe.patches.v7_1.rename_scheduler_log_to_error_log frappe.patches.v7_1.sync_language_doctype frappe.patches.v7_0.rename_bulk_email_to_email_queue frappe.patches.v7_1.rename_chinese_language_codes @@ -146,8 +147,12 @@ frappe.patches.v7_0.cleanup_list_settings execute:frappe.db.set_default('language', '') frappe.patches.v7_1.refactor_integration_broker frappe.patches.v7_1.set_backup_limit +frappe.patches.v7_1.fix_email_sender +execute:frappe.db.sql("update tabCommunication SET docstatus = 0 where docstatus =2 or docstatus = 1") +execute:frappe.db.sql("update tabCommunication set communication_date = if(actualdate,actualdate,creation) where time(communication_date) = 0 or actualdate is not null") frappe.patches.v7_1.disabled_print_settings_for_custom_print_format frappe.patches.v7_2.set_doctype_engine frappe.patches.v7_2.merge_knowledge_base frappe.patches.v7_0.update_report_builder_json +frappe.patches.v7_2.match_emails_to_contacts frappe.patches.v7_2.set_in_standard_filter_property #1 \ No newline at end of file diff --git a/frappe/patches/v6_24/imap_get_unique.py b/frappe/patches/v6_24/imap_get_unique.py new file mode 100644 index 0000000000..4e07947fd4 --- /dev/null +++ b/frappe/patches/v6_24/imap_get_unique.py @@ -0,0 +1,82 @@ +import frappe +import imaplib +import hashlib +import re +import email, email.utils,email.header +from email.header import decode_header +from frappe.email.receive import get_unique_id + +def execute(): + frappe.reload_doctype("Communication") + frappe.reload_doctype("Unhandled Email") + for email_account in frappe.get_list("Email Account", filters={"awaiting_password": 0}): + email_acc = frappe.get_doc("Email Account", email_account) + try: + email_server = email_acc.get_server(in_receive=True) + email_server.imap.select("Inbox") + #messages =email_server.imap.uid('fetch', "1:*", '(BODY.PEEK[HEADER.FIELDS (FROM TO ENVELOPE-TO DATE RECEIVED)])') + messages = email_server.imap.uid('fetch', "1:*",'(BODY.PEEK[HEADER])') + comms = frappe.db.sql("""select uid,name from `tabCommunication` + where email_account=%(email_account)s""",{"email_account":email_account.name},as_dict=1 ) + unhandled = frappe.db.sql("""select uid,name from `tabUnhandled Email` + where email_account=%(email_account)s""",{"email_account":email_account.name},as_dict=1 ) + count =0; + for i, item in enumerate(messages[1]): + if isinstance(item, tuple): + + # check for uid appended to the end + uid = re.search(r'UID [0-9]*', messages[1][i + 1], re.U | re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + + # check for uid at start + if not uid: + # for m in item: + uid = re.search(r'UID [0-9]*', messages[1][i][0], re.U | re.I) + if uid: + uid = uid.group()[4:] + else: + uid = "" + continue + mail = email.message_from_string(item[1]) + unique_id = get_unique_id(mail) + + #unique_id = hashlib.md5((mail.get("X-Original-From") or mail["From"])+(mail.get("To") or mail.get("Envelope-to"))+(mail.get("Received") or mail["Date"])).hexdigest() + found =False + for comm in comms: + if comm.uid == uid: + found =True + frappe.db.sql("""update `tabCommunication` + set unique_id = %(unique_id)s + where name = %(name)s + """, {"unique_id": unique_id, + "name":comm.name}) + + if not found: + for comm in unhandled: + if comm.uid == uid: + found = True + frappe.db.sql("""update `tabUnhandled Email` + set unique_id = %(unique_id)s + where name = %(name)s + """, {"unique_id": unique_id, + "name": comm.name}) + if found: + count += 1 + + #frappe.db.sql("""update `tabCommunication` + # set unique_id = %(unique_id)s + # where email_account= %(email_account)s and uid = %(uid)s + # """, {"unique_id": h, + # "email_account":email_account.name, + # "uid": uid}) + print email_account.name,count + except Exception, e: + print e + finally: + try: + email_server.imap.logout() + except: + pass diff --git a/frappe/patches/v7_1/fix_email_sender.py b/frappe/patches/v7_1/fix_email_sender.py new file mode 100644 index 0000000000..edddea4bf9 --- /dev/null +++ b/frappe/patches/v7_1/fix_email_sender.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +import frappe +from frappe.utils import extract_email_id +from inbox.email_inbox.contact import match_email_to_contact +def execute(): + frappe.reload_doctype('Communication') + for c in frappe.db.sql("""select name,sender from tabCommunication where communication_type = 'Communication' and sender like '%<%>'""",as_dict=1): + frappe.db.set_value('Communication', c.name, 'sender',extract_email_id(c.sender) , update_modified=False) + communication = frappe.get_doc('Communication', c.name) + match_email_to_contact(communication) + + \ No newline at end of file diff --git a/frappe/patches/v7_2/match_emails_to_contacts.py b/frappe/patches/v7_2/match_emails_to_contacts.py new file mode 100644 index 0000000000..68bca0d520 --- /dev/null +++ b/frappe/patches/v7_2/match_emails_to_contacts.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals +import frappe +def execute(): + frappe.db.sql("update `tabContact` set email_id = lower(email_id)") + frappe.db.sql("update `tabCommunication` set sender = lower(sender),recipients = lower(recipients)") + + + origin_contact = frappe.db.sql("select name,email_id,supplier,supplier_name,customer,customer_name,user,organisation from `tabContact` where email_id <>''",as_dict=1) + origin_communication = frappe.db.sql("select name, sender,recipients,sent_or_received from `tabCommunication` where communication_type = 'Communication'",as_dict=1) + + for communication in origin_communication: + # format contacts + for comm in origin_contact: + if (communication.sender and communication.sent_or_received == "Received" and communication.sender.find(comm.email_id) > -1) \ + or (communication.recipients !=None and communication.sent_or_received == "Sent" and communication.recipients.find(comm.email_id) > -1): + if sum(1 for x in [comm.supplier, comm.customer, comm.user,comm.organisation] if x) > 1: + frappe.db.sql("""update `tabCommunication` + set timeline_doctype = %(timeline_doctype)s, + timeline_name = %(timeline_name)s, + timeline_label = %(timeline_label)s + where name = %(name)s""", { + "timeline_doctype": "Contact", + "timeline_name": comm.name, + "timeline_label": comm.name, + "name": communication.name + }) + + elif comm.supplier: + frappe.db.sql("""update `tabCommunication` + set timeline_doctype = %(timeline_doctype)s, + timeline_name = %(timeline_name)s, + timeline_label = %(timeline_label)s + where name = %(name)s""", { + "timeline_doctype": "Supplier", + "timeline_name": comm.supplier, + "timeline_label": comm.supplier_name, + "name": communication.name + }) + + elif comm.customer: + frappe.db.sql("""update `tabCommunication` + set timeline_doctype = %(timeline_doctype)s, + timeline_name = %(timeline_name)s, + timeline_label = %(timeline_label)s + where name = %(name)s""", { + "timeline_doctype": "Customer", + "timeline_name": comm.customer, + "timeline_label": comm.customer_name, + "name": communication.name + }) + elif comm.user: + frappe.db.sql("""update `tabCommunication` + set timeline_doctype = %(timeline_doctype)s, + timeline_name = %(timeline_name)s, + timeline_label = %(timeline_label)s + where name = %(name)s""", { + "timeline_doctype": "User", + "timeline_name": comm.user, + "timeline_label": comm.user, + "name": communication.name + }) + elif comm.organisation: + frappe.db.sql("""update `tabCommunication` + set timeline_doctype = %(timeline_doctype)s, + timeline_name = %(timeline_name)s, + timeline_label = %(timeline_label)s + where name = %(name)s""", { + "timeline_doctype": "Organisation", + "timeline_name": comm.organisation, + "timeline_label": comm.organisation, + "name": communication.name + }) \ No newline at end of file diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 26c09186c0..13fb7d081c 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -84,8 +84,93 @@ frappe.Application = Class.extend({ }); dialog.get_close_btn().toggle(false); }); + if (sys_defaults.email_user_password){ + var email_list = sys_defaults.email_user_password.split(','); + for (u in email_list) { + if (email_list[u]===frappe.user.name){ + this.set_password(email_list[u]) + } + } + } + + }, + set_password: function (user) { + var me=this + frappe.call({ + method: 'frappe.core.doctype.user.user.get_email_awaiting', + args: { + "user": user + }, + callback: function (email_account) { + email_account = email_account["message"]; + if (email_account) { + var i = 0; + if (i < email_account.length) { + me.email_password_prompt( email_account, user, i); + } + } + } + }); }, + email_password_prompt: function(email_account,user,i) { + var me = this + var d = new frappe.ui.Dialog({ + title: __('Email Account setup please enter your password for: '+email_account[i]["email_id"]), + fields: [ + { 'fieldname': 'password', + 'fieldtype': 'Password', + 'label': 'Email Account Password', + 'reqd': 1 + }, + { + "fieldtype": "Button", + "label": __("Submit") + } + ] + }); + d.get_input("submit").on("click", function() { + //setup spinner + d.hide(); + var s = new frappe.ui.Dialog({ + title: __("Checking one moment"), + fields: [{ + "fieldtype": "HTML", + "fieldname": "checking" + }] + }); + s.fields_dict.checking.$wrapper.html('') + s.show(); + frappe.call({ + method: 'frappe.core.doctype.user.user.set_email_password', + args: { + "email_account": email_account[i]["email_account"], + "user": user, + "password": d.get_value("password") + }, + callback: function (passed) + { + s.hide(); + d.hide();//hide waiting indication + if (!passed["message"]) + { + show_alert("Login Failed please try again", 5); + me.email_password_prompt(email_account, user, i) + } + else + { + if (i + 1 < email_account.length) + { + i = i + 1; + me.email_password_prompt(email_account, user, i) + } + } + + } + }); + }); + d.show(); + }, load_bootinfo: function() { if(frappe.boot) { frappe.modules = {}; diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index 949d62c3ae..2f5b858a22 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -34,7 +34,13 @@ frappe.ui.form.Timeline = Class.extend({ } } }); - + this.list.on("click",".comment-header",function(e) { + if (!inList(["A","BUTTON"],e.target.tagName)) { + $(this).parent().find(".timeline-content-show").toggleClass("hide"); + $(this).find(".expand-icon").toggleClass("octicon-chevron-down octicon-chevron-up") + } + }) + this.email_button = this.wrapper.find(".btn-new-email") .on("click", function() { new frappe.views.CommunicationComposer({ @@ -96,7 +102,7 @@ frappe.ui.form.Timeline = Class.extend({ var communications = this.get_communications(true); - $.each(communications.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }), + $.each(communications.sort(function(a, b) { return a.communication_date > b.communication_date ? -1 : 1 }), function(i, c) { if(c.content) { c.frm = me.frm; @@ -121,7 +127,7 @@ frappe.ui.form.Timeline = Class.extend({ comment_type: "Created", communication_type: "Comment", sender: this.frm.doc.owner, - creation: this.frm.doc.creation, + communication_date: this.frm.doc.creation, frm: this.frm }); @@ -130,6 +136,8 @@ frappe.ui.form.Timeline = Class.extend({ this.frm.sidebar.refresh_comments(); this.frm.trigger('timeline_refresh'); + $(this.list.find(".comment-header")[0]).parent().find(".timeline-content-show").toggleClass("hide") + $(this.list.find(".comment-header")[0]).find(".expand-icon").toggleClass("octicon-chevron-down octicon-chevron-up") }, render_timeline_item: function(c) { @@ -187,6 +195,7 @@ frappe.ui.form.Timeline = Class.extend({ if(c.communication_type=="Communication" && c.communication_medium==="Email") { this.last_type = c.communication_medium; this.add_reply_btn_event($timeline_item, c); + this.add_relink_btn_event($timeline_item, c); } }, @@ -215,6 +224,18 @@ frappe.ui.form.Timeline = Class.extend({ }); }, + add_relink_btn_event: function($timeline_item, c) { + var me = this; + $timeline_item.find(".relink-link").on("click", function() { + var name = $(this).attr("data-name"); + callback= function (frm) { + $timeline_item.hide() + } + frappe.timeline.relink_dialog(name, cur_frm.doctype, cur_frm.name, callback) + }); + + }, + prepare_timeline_item: function(c) { if(!c.sender) c.sender = this.frm.doc.owner; @@ -240,7 +261,7 @@ frappe.ui.form.Timeline = Class.extend({ } } - c.comment_on = comment_when(c.creation); + c.comment_on = comment_when(c.communication_date || c.creation); c.fullname = c.sender_full_name || frappe.user.full_name(c.sender); if(c.attachments && typeof c.attachments==="string") @@ -272,6 +293,7 @@ frappe.ui.form.Timeline = Class.extend({ } else { c.content_html = c.content; c.content_html = frappe.utils.strip_whitespace(c.content_html); + c.content_html = c.content_html.replace(/</g,"<").replace(/>/g,">") } // bold @mentions @@ -294,7 +316,7 @@ frappe.ui.form.Timeline = Class.extend({ }, is_communication_or_comment: function(c) { - return c.communication_type==="Communication" || (c.communication_type==="Comment" && c.comment_type==="Comment"); + return c.communication_type==="Communication" || (c.communication_type==="Comment" && (c.comment_type==="Comment"||c.comment_type==="Relinked")); }, set_icon_and_color: function(c) { @@ -316,7 +338,8 @@ frappe.ui.form.Timeline = Class.extend({ "Shared": "octicon octicon-eye", "Unshared": "octicon octicon-circle-slash", "Like": "octicon octicon-heart", - "Edit": "octicon octicon-pencil" + "Edit": "octicon octicon-pencil", + "Relinked": "octicon octicon-check" }[c.comment_type || c.communication_medium] c.color = { @@ -333,7 +356,8 @@ frappe.ui.form.Timeline = Class.extend({ "Workflow": "#2c3e50", "Label": "#2c3e50", "Attachment": "#7f8c8d", - "Attachment Removed": "#eee" + "Attachment Removed": "#eee", + "Relinked": "#16a085" }[c.comment_type || c.communication_medium]; c.icon_fg = { @@ -547,7 +571,7 @@ frappe.ui.form.Timeline = Class.extend({ communications = this.frm.get_docinfo().communications, email = this.get_recipient(); - $.each(communications.sort(function(a, b) { return a.creation > b.creation ? -1 : 1 }), function(i, c) { + $.each(communications.sort(function(a, b) { return a.communication_date > b.communication_date ? -1 : 1 }), function(i, c) { if(c.communication_type=='Communication' && c.communication_medium=="Email") { if(from_recipient) { if(c.sender.indexOf(email)!==-1) { @@ -668,4 +692,52 @@ $.extend(frappe.timeline, { return index; }, + relink_dialog: function(name, reference_doctype, reference_name, callback, timeline_hide){ + var lib = "frappe.email"; + var d = new frappe.ui.Dialog ({ + title: __("Relink Communication"), + fields: [{ + "fieldtype": "Link", + "options": "DocType", + "label": __("Reference Doctype"), + "fieldname": "reference_doctype", + "get_query": function() {return {"query": lib +".get_communication_doctype"}} + }, + { + "fieldtype": "Dynamic Link", + "options": "reference_doctype", + "label": __("Reference Name"), + "fieldname": "reference_name" + }] + }); + d.set_value("reference_doctype", reference_doctype); + d.set_value("reference_name", reference_name); + d.set_primary_action(__("Relink"), function (frm) { + values = d.get_values(); + if (values) { + frappe.confirm( + 'Are you sure you want to relink this communication to ' + values["reference_name"] + '?', + function () { + d.hide(); + frappe.call + ({ + method: lib + ".relink", + args: { + "name": timeline_hide ? timeline_hide: name, + "reference_doctype": values["reference_doctype"], + "reference_name": values["reference_name"] + }, + callback: function (frm) { + callback(frm) + } + }) + }, + function () { + show_alert('Document not Relinked') + } + ) + } + }); + d.show(); + } }) diff --git a/frappe/public/js/frappe/form/footer/timeline_item.html b/frappe/public/js/frappe/form/footer/timeline_item.html index 1bc5df9081..f15cf0aed8 100755 --- a/frappe/public/js/frappe/form/footer/timeline_item.html +++ b/frappe/public/js/frappe/form/footer/timeline_item.html @@ -25,9 +25,9 @@ {% if(data.communication_type==="Communication" || (data.communication_type==="Comment" && data.comment_type==="Comment")) { %} - -
+
+ {%= data.fullname %} {% if (data.timeline_doctype===data.frm.doc.doctype @@ -39,6 +39,15 @@ {% } %} + {% if(data.communication_type==="Communication") { %} + + {% } %} – {%= data.comment_on %} {% if(data.communication_type==="Communication") { %} @@ -72,9 +81,14 @@ {% if (frappe.model.can_read(\'Communication\')) { %} {% } %} + + {% if (data.communication_medium === "Email") { %} + – {%= __("Relink") %} + {% } %} {% if (data.communication_medium === "Email") { %} - {%= __("Reply") %} {% } %} {% } %} @@ -90,10 +104,26 @@ {{ (data._liked_by || []).length }}
-
+
{%= data.content_html %}
+ {% if(data.attachments && data.attachments.length) { %} +
+ {% $.each(data.attachments, function(i, a) { %} + + {% }); %} +
+ {% } %}
{% } else if(in_list(["Assignment Completed", "Assigned", "Shared", @@ -148,22 +178,6 @@ – {%= data.comment_on %}
{% } %} - {% if(data.attachments && data.attachments.length) { %} -
- {% $.each(data.attachments, function(i, a) { %} - - {% }); %} -
- {% } %}
diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 12dedfddb7..638e28b6f9 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -47,15 +47,18 @@ frappe.views.CommunicationComposer = Class.extend({ }, get_fields: function() { - return [ - {label:__("To"), fieldtype:"Data", reqd: 0, fieldname:"recipients"}, - {fieldtype: "Section Break", collapsible: 1, label: __("CC & Standard Reply")}, - {label:__("CC"), fieldtype:"Data", fieldname:"cc"}, + this.from = {}; + this.get_from(); + var fields= [ + this.from , + {label:__("To"), fieldtype:"Data", reqd: 0, fieldname:"recipients",length:524288}, + {fieldtype: "Section Break", collapsible: 1, label: "CC & Standard Reply"}, + {label:__("CC"), fieldtype:"Data", fieldname:"cc",length:524288}, {label:__("Standard Reply"), fieldtype:"Link", options:"Standard Reply", fieldname:"standard_reply"}, {fieldtype: "Section Break"}, {label:__("Subject"), fieldtype:"Data", reqd: 1, - fieldname:"subject"}, + fieldname:"subject",length:524288}, {fieldtype: "Section Break"}, {label:__("Message"), fieldtype:"Text Editor", reqd: 1, fieldname:"content"}, @@ -65,6 +68,8 @@ frappe.views.CommunicationComposer = Class.extend({ fieldname:"send_email"}, {label:__("Send me a copy"), fieldtype:"Check", fieldname:"send_me_a_copy"}, + {label:__("Send Read Receipt"), fieldtype:"Check", + fieldname:"send_read_receipt"}, {label:__("Communication Medium"), fieldtype:"Select", options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"], fieldname:"communication_medium"}, @@ -81,8 +86,33 @@ frappe.views.CommunicationComposer = Class.extend({ {label:__("Select Attachments"), fieldtype:"HTML", fieldname:"select_attachments"} ]; + if(!fields[1]){//removes from if doesnt have assigned email + fields.splice(1,1) + } + return fields }, + get_from:function(){ + var me = this; + frappe.call({ + method: 'frappe.email.page.email_inbox.get_accounts', + args: {user: frappe.user["name"]}, + async: false, + callback: function (list) { + if (list["message"]) { + var accounts = []; + for (var i =0;i2&&!this.$element.is("ul"))throw"in Bootstrap version 3 the pagination root item must be an ul element.";this.currentPage=1,this.lastPage=1,this.setOptions(options),this.initialized=!0},setOptions:function(options){this.options=$.extend({},this.options||$.fn.bootstrapPaginator.defaults,options),this.totalPages=parseInt(this.options.totalPages,10),this.numberOfPages=parseInt(this.options.numberOfPages,10),options&&"undefined"!=typeof options.currentPage&&this.setCurrentPage(options.currentPage),this.listen(),this.render(),this.initialized||this.lastPage===this.currentPage||this.$element.trigger("page-changed",[this.lastPage,this.currentPage])},listen:function(){this.$element.off("page-clicked"),this.$element.off("page-changed"),"function"==typeof this.options.onPageClicked&&this.$element.bind("page-clicked",this.options.onPageClicked),"function"==typeof this.options.onPageChanged&&this.$element.on("page-changed",this.options.onPageChanged),this.$element.bind("page-clicked",this.onPageClicked)},destroy:function(){this.$element.off("page-clicked"),this.$element.off("page-changed"),this.$element.removeData("bootstrapPaginator"),this.$element.empty()},show:function(page){this.setCurrentPage(page),this.render(),this.lastPage!==this.currentPage&&this.$element.trigger("page-changed",[this.lastPage,this.currentPage])},showNext:function(){var pages=this.getPages();pages.next&&this.show(pages.next)},showPrevious:function(){var pages=this.getPages();pages.prev&&this.show(pages.prev)},showFirst:function(){var pages=this.getPages();pages.first&&this.show(pages.first)},showLast:function(){var pages=this.getPages();pages.last&&this.show(pages.last)},onPageItemClicked:function(event){var type=event.data.type,page=event.data.page;this.$element.trigger("page-clicked",[event,type,page])},onPageClicked:function(event,originalEvent,type,page){var currentTarget=$(event.currentTarget);switch(type){case"first":currentTarget.bootstrapPaginator("showFirst");break;case"prev":currentTarget.bootstrapPaginator("showPrevious");break;case"next":currentTarget.bootstrapPaginator("showNext");break;case"last":currentTarget.bootstrapPaginator("showLast");break;case"page":currentTarget.bootstrapPaginator("show",page)}},render:function(){var containerClass=this.getValueFromOption(this.options.containerClass,this.$element),size=this.options.size||"normal",alignment=this.options.alignment||"left",pages=this.getPages(),listContainer=2===this.options.bootstrapMajorVersion?$("
    "):this.$element,listContainerClass=2===this.options.bootstrapMajorVersion?this.getValueFromOption(this.options.listContainerClass,listContainer):null,first=null,prev=null,next=null,last=null,p=null,i=0;switch(this.$element.prop("class",""),this.$element.addClass("pagination"),size.toLowerCase()){case"large":case"small":case"mini":this.$element.addClass($.fn.bootstrapPaginator.sizeArray[this.options.bootstrapMajorVersion][size.toLowerCase()])}if(2===this.options.bootstrapMajorVersion)switch(alignment.toLowerCase()){case"center":this.$element.addClass("pagination-centered");break;case"right":this.$element.addClass("pagination-right")}for(this.$element.addClass(containerClass),this.$element.empty(),2===this.options.bootstrapMajorVersion&&(this.$element.append(listContainer),listContainer.addClass(listContainerClass)),this.pageRef=[],pages.first&&(first=this.buildPageItem("first",pages.first),first&&listContainer.append(first)),pages.prev&&(prev=this.buildPageItem("prev",pages.prev),prev&&listContainer.append(prev)),i=0;i"),itemContent=$(""),text="",title="",itemContainerClass=this.options.itemContainerClass(type,page,this.currentPage),itemContentClass=this.getValueFromOption(this.options.itemContentClass,type,page,this.currentPage),tooltipOpts=null;switch(type){case"first":if(!this.getValueFromOption(this.options.shouldShowPage,type,page,this.currentPage))return;text=this.options.itemTexts(type,page,this.currentPage),title=this.options.tooltipTitles(type,page,this.currentPage);break;case"last":if(!this.getValueFromOption(this.options.shouldShowPage,type,page,this.currentPage))return;text=this.options.itemTexts(type,page,this.currentPage),title=this.options.tooltipTitles(type,page,this.currentPage);break;case"prev":if(!this.getValueFromOption(this.options.shouldShowPage,type,page,this.currentPage))return;text=this.options.itemTexts(type,page,this.currentPage),title=this.options.tooltipTitles(type,page,this.currentPage);break;case"next":if(!this.getValueFromOption(this.options.shouldShowPage,type,page,this.currentPage))return;text=this.options.itemTexts(type,page,this.currentPage),title=this.options.tooltipTitles(type,page,this.currentPage);break;case"page":if(!this.getValueFromOption(this.options.shouldShowPage,type,page,this.currentPage))return;text=this.options.itemTexts(type,page,this.currentPage),title=this.options.tooltipTitles(type,page,this.currentPage)}return itemContainer.addClass(itemContainerClass).append(itemContent),itemContent.addClass(itemContentClass).html(text).on("click",null,{type:type,page:page},$.proxy(this.onPageItemClicked,this)),this.options.pageUrl&&itemContent.attr("href",this.getValueFromOption(this.options.pageUrl,type,page,this.currentPage)),this.options.useBootstrapTooltip?(tooltipOpts=$.extend({},this.options.bootstrapTooltipOptions,{title:title}),itemContent.tooltip(tooltipOpts)):itemContent.attr("title",title),itemContainer},setCurrentPage:function(page){if(page>this.totalPages||1>page)throw"Page out of range";this.lastPage=this.currentPage,this.currentPage=parseInt(page,10)},getPages:function(){var totalPages=this.totalPages,pageStart=0===this.currentPage%this.numberOfPages?(parseInt(this.currentPage/this.numberOfPages,10)-1)*this.numberOfPages+1:parseInt(this.currentPage/this.numberOfPages,10)*this.numberOfPages+1,output=[],i=0,counter=0;for(pageStart=1>pageStart?1:pageStart,i=pageStart,counter=0;counter=i;i+=1,counter+=1)output.push(i);return output.first=1,output.prev=this.currentPage>1?this.currentPage-1:1,output.next=this.currentPage Date: Fri, 13 Jan 2017 18:52:41 +0530 Subject: [PATCH 14/14] [enhance] moved contact to frappe :boom: --- .../doctype/communication/communication.json | 2830 ++++++++--------- frappe/core/doctype/dynamic_link/__init__.py | 0 .../doctype/dynamic_link/dynamic_link.json | 97 + .../core/doctype/dynamic_link/dynamic_link.py | 13 + frappe/core/doctype/user/user.json | 92 +- .../core/doctype/user_email/user_email.json | 7 +- frappe/desk/reportview.py | 24 + frappe/email/doctype/contact/__init__.py | 0 frappe/email/doctype/contact/contact.js | 41 + frappe/email/doctype/contact/contact.json | 805 +++++ frappe/email/doctype/contact/contact.py | 130 + frappe/email/doctype/contact/test_contact.py | 12 + .../email/doctype/contact/test_records.json | 22 + frappe/email/page/email_inbox/email_inbox.js | 38 +- frappe/geo/doctype/address/__init__.py | 0 frappe/geo/doctype/address/address.js | 25 + frappe/geo/doctype/address/address.json | 651 ++++ frappe/geo/doctype/address/address.py | 203 ++ frappe/geo/doctype/address/test_address.py | 18 + frappe/geo/doctype/address/test_records.json | 15 + .../geo/doctype/address_template/__init__.py | 0 .../address_template/address_template.js | 16 + .../address_template/address_template.json | 148 + .../address_template/address_template.py | 43 + .../address_template/test_address_template.py | 24 + .../address_template/test_records.json | 13 + frappe/hooks.py | 4 + frappe/patches.txt | 4 - frappe/patches/v7_1/fix_email_sender.py | 12 - .../patches/v7_2/match_emails_to_contacts.py | 72 - frappe/public/js/legacy/clientscriptAPI.js | 4 +- 31 files changed, 3805 insertions(+), 1558 deletions(-) create mode 100644 frappe/core/doctype/dynamic_link/__init__.py create mode 100644 frappe/core/doctype/dynamic_link/dynamic_link.json create mode 100644 frappe/core/doctype/dynamic_link/dynamic_link.py create mode 100644 frappe/email/doctype/contact/__init__.py create mode 100644 frappe/email/doctype/contact/contact.js create mode 100644 frappe/email/doctype/contact/contact.json create mode 100644 frappe/email/doctype/contact/contact.py create mode 100644 frappe/email/doctype/contact/test_contact.py create mode 100644 frappe/email/doctype/contact/test_records.json create mode 100644 frappe/geo/doctype/address/__init__.py create mode 100644 frappe/geo/doctype/address/address.js create mode 100644 frappe/geo/doctype/address/address.json create mode 100644 frappe/geo/doctype/address/address.py create mode 100644 frappe/geo/doctype/address/test_address.py create mode 100644 frappe/geo/doctype/address/test_records.json create mode 100644 frappe/geo/doctype/address_template/__init__.py create mode 100644 frappe/geo/doctype/address_template/address_template.js create mode 100644 frappe/geo/doctype/address_template/address_template.json create mode 100644 frappe/geo/doctype/address_template/address_template.py create mode 100644 frappe/geo/doctype/address_template/test_address_template.py create mode 100644 frappe/geo/doctype/address_template/test_records.json delete mode 100644 frappe/patches/v7_1/fix_email_sender.py delete mode 100644 frappe/patches/v7_2/match_emails_to_contacts.py diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index c968b67404..84a491be86 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -1,1465 +1,1465 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2013-01-29 10:47:14", - "custom": 0, - "description": "Keep a track of all communications", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2013-01-29 10:47:14", + "custom": 0, + "description": "Keep a track of all communications", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subject", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Subject", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "subject", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Subject", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "eval:doc.communication_type==='Communication'", - "columns": 0, - "fieldname": "section_break_10", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": "eval:doc.communication_type==='Communication'", + "columns": 0, + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "communication_medium", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "\nEmail\nChat\nPhone\nSMS\nVisit\nOther", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "communication_medium", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Type", + "length": 0, + "no_copy": 0, + "options": "\nEmail\nChat\nPhone\nSMS\nVisit\nOther", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "sender", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From", - "length": 255, - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "sender", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From", + "length": 255, + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "recipients", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "recipients", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "cc", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "CC", - "length": 0, - "no_copy": 0, - "options": "Email", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "cc", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "CC", + "length": 0, + "no_copy": 0, + "options": "Email", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:in_list([\"Phone\", \"SMS\"], doc.communication_medium)", - "fieldname": "phone_no", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Phone No.", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:in_list([\"Phone\", \"SMS\"], doc.communication_medium)", + "fieldname": "phone_no", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Phone No.", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Integrations can use this field to set email delivery status", - "fieldname": "delivery_status", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Delivery Status", - "length": 0, - "no_copy": 0, - "options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed\nError\nExpired\nSending", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Integrations can use this field to set email delivery status", + "fieldname": "delivery_status", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Delivery Status", + "length": 0, + "no_copy": 0, + "options": "\nSent\nBounced\nOpened\nMarked As Spam\nRejected\nDelayed\nSoft-Bounced\nClicked\nRecipient Unsubscribed\nError\nExpired\nSending", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "content", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Message", - "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, - "unique": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "content", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Message", + "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, + "unique": 0, "width": "400" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "status_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "status_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "text_content", - "fieldtype": "Code", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Text Content", - "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "text_content", + "fieldtype": "Code", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Text Content", + "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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Communication", - "fieldname": "communication_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Communication Type", - "length": 0, - "no_copy": 0, - "options": "Communication\nComment\nChat\nBot\nNotification", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Communication", + "fieldname": "communication_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Communication Type", + "length": 0, + "no_copy": 0, + "options": "Communication\nComment\nChat\nBot\nNotification", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "comment_type", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Comment Type", - "length": 0, - "no_copy": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "comment_type", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Comment Type", + "length": 0, + "no_copy": 0, "options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nUpdated\nSubmitted\nCancelled\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot\nRelinked", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nReplied\nClosed\nLinked", - "permlevel": 0, - "precision": "", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_type===\"Communication\"", - "fieldname": "sent_or_received", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Sent or Received", - "length": 0, - "no_copy": 0, - "options": "Sent\nReceived", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "additional_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Now", - "fieldname": "communication_date", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Date", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "read_receipt", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_standard_filter": 0, - "in_list_view": 0, - "label": "Sent Read Receipt", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sender_full_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Full Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "reference_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_doctype", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference Name", - "length": 0, - "no_copy": 0, - "options": "reference_doctype", - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_owner", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference Owner", - "length": 0, - "no_copy": 0, - "options": "reference_name.owner", - "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": 1, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.communication_medium===\"Email\"", - "fieldname": "email_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email Account", - "length": 0, - "no_copy": 0, - "options": "Email Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "in_reply_to", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "In Reply To", - "length": 0, - "no_copy": 0, - "options": "Communication", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "__user", - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_27", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "link_doctype", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Link DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "link_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Link Name", - "length": 0, - "no_copy": 0, - "options": "link_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timeline_doctype", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Timeline DocType", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timeline_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Timeline Name", - "length": 0, - "no_copy": 0, - "options": "timeline_doctype", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "timeline_label", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Timeline field Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "0", - "fieldname": "unread_notification_sent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unread Notification Sent", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "seen", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Seen", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "message_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Message ID", - "length": 995, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "_user_tags", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "User Tags", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "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, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "email_inbox", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Email Inbox", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "uid", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "UID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "message_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_list_view": 0, - "label": "Message ID", - "length": 995, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "unique_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, - "label": "Unique id", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 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, "unique": 0 - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "deleted", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Deleted", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "actualdate", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "ActualDate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "nomatch", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "No Match", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "has_attachment", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Has Attachment", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "timeline_hide", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, - "label": "Hidden in Timeline", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Open\nReplied\nClosed\nLinked", + "permlevel": 0, + "precision": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_type===\"Communication\"", + "fieldname": "sent_or_received", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Sent or Received", + "length": 0, + "no_copy": 0, + "options": "Sent\nReceived", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "additional_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "More Information", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Now", + "fieldname": "communication_date", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Date", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "read_receipt", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_standard_filter": 0, + "in_list_view": 0, + "label": "Sent Read Receipt", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sender_full_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From Full Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "reference_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Name", + "length": 0, + "no_copy": 0, + "options": "reference_doctype", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_owner", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reference Owner", + "length": 0, + "no_copy": 0, + "options": "reference_name.owner", + "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": 1, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.communication_medium===\"Email\"", + "fieldname": "email_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Email Account", + "length": 0, + "no_copy": 0, + "options": "Email Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "in_reply_to", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "In Reply To", + "length": 0, + "no_copy": 0, + "options": "Communication", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "__user", + "fieldname": "user", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_27", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "link_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Link DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "link_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Link Name", + "length": 0, + "no_copy": 0, + "options": "link_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "timeline_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Timeline DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "timeline_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Timeline Name", + "length": 0, + "no_copy": 0, + "options": "timeline_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "timeline_label", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Timeline field Name", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "default": "0", + "fieldname": "unread_notification_sent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Unread Notification Sent", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "seen", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Seen", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "message_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Message ID", + "length": 995, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "_user_tags", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "User Tags", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "fieldname": "email_inbox", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Email Inbox", + "length": 0, + "no_copy": 0, + "permlevel": 1, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "uid", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "UID", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "message_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 1, + "in_filter": 0, + "in_list_view": 0, + "label": "Message ID", + "length": 995, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "unique_id", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Unique id", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "deleted", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Deleted", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "actualdate", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "ActualDate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "nomatch", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "No Match", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "has_attachment", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Has Attachment", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "fieldname": "timeline_hide", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Hidden in Timeline", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, "unique": 0 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-comment", - "idx": 1, - "image_view": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-12-29 14:39:53.460122", - "modified_by": "Administrator", - "module": "Core", - "name": "Communication", - "owner": "Administrator", + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-comment", + "idx": 1, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-12-29 14:39:53.460123", + "modified_by": "Administrator", + "module": "Core", + "name": "Communication", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "apply_user_permissions": 1, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 1, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "user_permission_doctypes": "[\"Email Account\"]", + "amend": 0, + "apply_user_permissions": 1, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 1, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "All", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "user_permission_doctypes": "[\"Email Account\"]", "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "subject", - "sort_order": "DESC", - "title_field": "subject", - "track_changes": 1, + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "subject", + "sort_order": "DESC", + "title_field": "subject", + "track_changes": 1, "track_seen": 0 } diff --git a/frappe/core/doctype/dynamic_link/__init__.py b/frappe/core/doctype/dynamic_link/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/dynamic_link/dynamic_link.json b/frappe/core/doctype/dynamic_link/dynamic_link.json new file mode 100644 index 0000000000..cde3702896 --- /dev/null +++ b/frappe/core/doctype/dynamic_link/dynamic_link.json @@ -0,0 +1,97 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-01-13 04:55:18.835023", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "link_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Link DocType", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "link_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Link Name", + "length": 0, + "no_copy": 0, + "options": "link_doctype", + "permlevel": 0, + "precision": "", + "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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-01-13 04:55:18.835023", + "modified_by": "Administrator", + "module": "Core", + "name": "Dynamic Link", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/dynamic_link/dynamic_link.py b/frappe/core/doctype/dynamic_link/dynamic_link.py new file mode 100644 index 0000000000..21f82f522a --- /dev/null +++ b/frappe/core/doctype/dynamic_link/dynamic_link.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, 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 DynamicLink(Document): + pass + +def on_doctype_update(): + frappe.db.add_index("Dynamic Link", ["link_doctype", "link_name"]) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 627c5a3d90..4741be8bd2 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -832,36 +832,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "users will have user permissions created for each account assigned", - "fieldname": "user_emails", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "User Emails", - "length": 0, - "no_copy": 0, - "options": "User Email", - "permlevel": 1, - "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, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -918,6 +888,64 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "email_inbox", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Email Inbox", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "user_emails", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "User Emails", + "length": 0, + "no_copy": 0, + "options": "User Email", + "permlevel": 1, + "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, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1094,7 +1122,7 @@ "collapsible": 1, "columns": 0, "default": "", - "description": "Uncheck modules to hide from user's desktop", + "description": "", "fieldname": "modules_access", "fieldtype": "Section Break", "hidden": 0, @@ -1785,7 +1813,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2017-01-10 08:47:42.781330", + "modified": "2017-01-13 07:10:40.266109", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user_email/user_email.json b/frappe/core/doctype/user_email/user_email.json index c784e3adc4..9b7e652729 100644 --- a/frappe/core/doctype/user_email/user_email.json +++ b/frappe/core/doctype/user_email/user_email.json @@ -8,7 +8,7 @@ "docstatus": 0, "doctype": "DocType", "document_type": "", - "editable_grid": 0, + "editable_grid": 1, "fields": [ { "allow_on_submit": 0, @@ -23,7 +23,7 @@ "in_filter": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "email account", + "label": "Email Account", "length": 0, "no_copy": 0, "options": "Email Account", @@ -108,7 +108,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-01-05 14:52:38.744801", + "modified": "2017-01-13 07:07:40.049130", "modified_by": "Administrator", "module": "Core", "name": "User Email", @@ -120,5 +120,6 @@ "read_only_onload": 0, "sort_field": "modified", "sort_order": "DESC", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 5f5bbe7c36..725f495361 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -282,3 +282,27 @@ def build_match_conditions(doctype, as_condition=True): return match_conditions.replace("%", "%%") else: return match_conditions + +def get_filters_cond(doctype, filters, conditions): + if filters: + flt = filters + if isinstance(filters, dict): + filters = filters.items() + flt = [] + for f in filters: + if isinstance(f[1], basestring) and f[1][0] == '!': + flt.append([doctype, f[0], '!=', f[1][1:]]) + else: + value = frappe.db.escape(f[1]) if isinstance(f[1], basestring) else f[1] + flt.append([doctype, f[0], '=', value]) + + query = DatabaseQuery(doctype) + query.filters = flt + query.conditions = conditions + query.build_filter_conditions(flt, conditions) + + cond = ' and ' + ' and '.join(query.conditions) + else: + cond = '' + return cond + diff --git a/frappe/email/doctype/contact/__init__.py b/frappe/email/doctype/contact/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/email/doctype/contact/contact.js b/frappe/email/doctype/contact/contact.js new file mode 100644 index 0000000000..149506a761 --- /dev/null +++ b/frappe/email/doctype/contact/contact.js @@ -0,0 +1,41 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt + + +cur_frm.email_field = "email_id"; +frappe.ui.form.on("Contact", { + refresh: function(frm) { + if(frm.doc.__islocal) { + var last_route = frappe.route_history.slice(-2, -1)[0]; + if(frappe.contact_link && frappe.contact_link.doc + && frappe.contact_link.doc.name==last_route[2]) { + frm.add_child('links', { + link_doctype: frappe.contact_link.doctype, + link_name: frappe.contact_link.doc[frappe.contact_link.fieldname] + }); + } + } + + if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) { + frm.add_custom_button(__("Invite as User"), function() { + frappe.call({ + method: "erpnext.utilities.doctype.contact.contact.invite_user", + args: { + contact: frm.doc.name + }, + callback: function(r) { + frm.set_value("user", r.message); + } + }); + }); + } + }, + validate: function(frm) { + // clear linked customer / supplier / sales partner on saving... + if(frm.doc.links) { + frm.doc.links.forEach(function(d) { + frappe.model.remove_from_locals(d.link_doctype, d.link_name); + }); + } + } +}); \ No newline at end of file diff --git a/frappe/email/doctype/contact/contact.json b/frappe/email/doctype/contact/contact.json new file mode 100644 index 0000000000..ae247f8ce4 --- /dev/null +++ b/frappe/email/doctype/contact/contact.json @@ -0,0 +1,805 @@ +{ + "allow_copy": 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", + "fields": [ + { + "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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-user", + "idx": 1, + "image_field": "image", + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-13 06:59:06.417300", + "modified_by": "Administrator", + "module": "Email", + "name": "Contact", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Master Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Master Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "match": "", + "permlevel": 1, + "print": 0, + "read": 1, + "report": 1, + "role": "All", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 0 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "sort_order": "ASC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/email/doctype/contact/contact.py b/frappe/email/doctype/contact/contact.py new file mode 100644 index 0000000000..ac878c74cd --- /dev/null +++ b/frappe/email/doctype/contact/contact.py @@ -0,0 +1,130 @@ +# 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 +from frappe.utils import cstr, has_gravatar +from frappe import _ +from frappe.model.document import Document + +class Contact(Document): + def autoname(self): + # concat first and last name + self.name = " ".join(filter(None, + [cstr(self.get(f)).strip() for f in ["first_name", "last_name"]])) + + # concat party name if reqd + for link in self.links: + self.name = self.name + '-' + link.link_name.strip() + break + + def validate(self): + self.set_user() + if self.email_id: + self.image = has_gravatar(self.email_id) + + def set_user(self): + if not self.user and self.email_id: + self.user = frappe.db.get_value("User", {"email": self.email_id}) + + def on_trash(self): + frappe.db.sql("""update `tabIssue` set contact='' where contact=%s""", + self.name) + + def has_common_link(self, doc): + reference_links = [(link.link_doctype, link.link_name) for link in doc.links] + for link in self.links: + if (link.link_doctype, link.link_name) in reference_links: + return True + + +def get_default_contact(doctype, name): + '''Returns default contact for the given doctype, name''' + out = frappe.db.sql('''select contact.name + from + tabContact contact, `tabDynamic Link` dl + where + dl.parent = contact.name and + dl.link_doctype=%s and + dl.link_name=%s and + dl.parenttype = "Contact" + order by + contact.is_primary_contact desc, name + limit 1''', (doctype, name), debug=1) + + print out + return out and out[0][0] or None + +@frappe.whitelist() +def invite_user(contact): + contact = frappe.get_doc("Contact", contact) + + if not contact.email_id: + frappe.throw(_("Please set Email Address")) + + if contact.has_permission("write"): + user = frappe.get_doc({ + "doctype": "User", + "first_name": contact.first_name, + "last_name": contact.last_name, + "email": contact.email_id, + "user_type": "Website User", + "send_welcome_email": 1 + }).insert(ignore_permissions = True) + + return user.name + +@frappe.whitelist() +def get_contact_details(contact): + contact = frappe.get_doc("Contact", contact) + out = { + "contact_person": contact.get("name"), + "contact_display": " ".join(filter(None, + [contact.get("first_name"), contact.get("last_name")])), + "contact_email": contact.get("email_id"), + "contact_mobile": contact.get("mobile_no"), + "contact_phone": contact.get("phone"), + "contact_designation": contact.get("designation"), + "contact_department": contact.get("department") + } + return out + +def update_contact(doc, method): + '''Update contact when user is updated, if contact is found. Called via hooks''' + contact_name = frappe.db.get_value("Contact", {"email_id": doc.name}) + if contact_name: + contact = frappe.get_doc("Contact", contact_name) + for key in ("first_name", "last_name", "phone"): + if doc.get(key): + contact.set(key, doc.get(key)) + contact.flags.ignore_mandatory = True + contact.save(ignore_permissions=True) + +def contact_query(doctype, txt, searchfield, start, page_len, filters): + from frappe.desk.reportview import get_match_cond + + return frappe.db.sql("""select + contact.name, contact.first_name, contact.last_name + from + tabContact as contact, `tabDynamic Link` as dl + where + dl.parent = contact.name and + dl.parenttype = 'Contact' and + dl.link_doctype = %(link_doctype)s and + dl.link_name = %(link_name)s and + contact.`{key}` like %(txt)s + {mcond} + order by + if(locate(%(_txt)s, contact.name), locate(%(_txt)s, contact.name), 99999), + contact.idx desc, contact.name + limit %(start)s, %(page_len)s """.format( + mcond=get_match_cond(doctype), + key=frappe.db.escape(searchfield)), + { + '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') + }) diff --git a/frappe/email/doctype/contact/test_contact.py b/frappe/email/doctype/contact/test_contact.py new file mode 100644 index 0000000000..99b6581a73 --- /dev/null +++ b/frappe/email/doctype/contact/test_contact.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +test_records = frappe.get_test_records('Contact') + +class TestContact(unittest.TestCase): + pass diff --git a/frappe/email/doctype/contact/test_records.json b/frappe/email/doctype/contact/test_records.json new file mode 100644 index 0000000000..e319745385 --- /dev/null +++ b/frappe/email/doctype/contact/test_records.json @@ -0,0 +1,22 @@ +[ + { + "customer": "_Test Customer", + "customer_name": "_Test Customer", + "doctype": "Contact", + "email_id": "test_contact_customer@example.com", + "first_name": "_Test Contact For _Test Customer", + "is_primary_contact": 1, + "phone": "+91 0000000000", + "status": "Open" + }, + { + "doctype": "Contact", + "email_id": "test_contact_supplier@example.com", + "first_name": "_Test Contact For _Test Supplier", + "is_primary_contact": 1, + "phone": "+91 0000000000", + "status": "Open", + "supplier": "_Test Supplier", + "supplier_name": "_Test Supplier" + } +] \ No newline at end of file diff --git a/frappe/email/page/email_inbox/email_inbox.js b/frappe/email/page/email_inbox/email_inbox.js index 7a6ef5711b..36989bd561 100644 --- a/frappe/email/page/email_inbox/email_inbox.js +++ b/frappe/email/page/email_inbox/email_inbox.js @@ -71,16 +71,16 @@ frappe.Inbox = frappe.ui.Listing.extend({ } me.fresh = false }); - }else{ - alert("No Email Account assigned to you contact your System administrator"); - if (frappe.session.user==="Administrator") - { - frappe.set_route("List", "User"); - } - else - { - window.history.back(); - } + } else { + frappe.msgprint(__("No Email Account assigned to you. Please contact your System Administrator")); + + setTimeout(function() { + if (frappe.session.user==="Administrator") { + frappe.set_route("List", "User"); + } else { + frappe.set_route(''); + } + }, 3000); } }, refresh:function(){ @@ -259,7 +259,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ "fieldname":"newcontact", "description": __('Create new Contact for a Customer, Supplier, User or Organisation to Match "') + row.sender + __('" Against') } - + ]; if (!nomatch) { fields.push({ @@ -283,7 +283,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ frappe.route_titles["create_contact"] = 1; var name_split = row.sender_full_name?row.sender_full_name.split(' '):["",""]; row.nomatch = 1; - + frappe.route_options = { "email_id": row.sender, "first_name": name_split[0], @@ -320,7 +320,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ return } me.open_email = row.name - + //mark email as read if(me.account!="Sent") { this.mark_read(row); @@ -341,7 +341,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ me.company_select(row) }}, 4000); } - + var c = me.prepare_email(row); emailitem.fields_dict.email.$wrapper.html(frappe.render_template("inbox_email", {data:c})); $(emailitem.$wrapper).find(".reply").find("a").attr("target", "_blank"); @@ -356,7 +356,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ me.delete_email({n:row.name, u:row.uid}); emailitem.hide() }); - + $(emailitem.$wrapper).find(".company-link").on("click", function () { me.company_select(row, true)}); me.add_reply_btn_event(emailitem, c); @@ -365,12 +365,12 @@ frappe.Inbox = frappe.ui.Listing.extend({ $(".modal-dialog").addClass("modal-lg"); $(emailitem.$wrapper).find(".modal-title").parent().removeClass("col-xs-7").addClass("col-xs-7 col-sm-8 col-md-9"); $(emailitem.$wrapper).find(".text-right").parent().removeClass("col-xs-5").addClass("col-xs-5 col-sm-4 col-md-3"); - + //setup close emailitem.onhide = function() { me.open_email = null } - + emailitem.show(); }, add_reply_btn_event: function (emailitem, c) { @@ -437,7 +437,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ forward:true, attachments:c.attachments }); - + $(communication.dialog.fields_dict.select_attachments.wrapper).find("input[type=checkbox]").prop("checked",true) }); }, @@ -582,7 +582,7 @@ frappe.Inbox = frappe.ui.Listing.extend({ var names = [{name:data.name, uid:data.uid}] } //could add flag to sync deletes but not going to as keeps history - + me.update_local_flags(names, "deleted", "1") }, mark_unread:function(){ diff --git a/frappe/geo/doctype/address/__init__.py b/frappe/geo/doctype/address/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/geo/doctype/address/address.js b/frappe/geo/doctype/address/address.js new file mode 100644 index 0000000000..7305e5061d --- /dev/null +++ b/frappe/geo/doctype/address/address.js @@ -0,0 +1,25 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Address", { + refresh: function(frm) { + if(frm.doc.__islocal) { + var last_route = frappe.route_history.slice(-2, -1)[0]; + if(frappe.contact_link && frappe.contact_link.doc + && frappe.contact_link.doc.name==last_route[2]) { + frm.add_child('links', { + link_doctype: frappe.contact_link.doctype, + link_name: frappe.contact_link.doc[frappe.contact_link.fieldname] + }); + } + } + }, + validate: function(frm) { + // clear linked customer / supplier / sales partner on saving... + if(frm.doc.links) { + frm.doc.links.forEach(function(d) { + frappe.model.remove_from_locals(d.link_doctype, d.link_name); + }); + } + } +}); diff --git a/frappe/geo/doctype/address/address.json b/frappe/geo/doctype/address/address.json new file mode 100644 index 0000000000..969f9b0f69 --- /dev/null +++ b/frappe/geo/doctype/address/address.json @@ -0,0 +1,651 @@ +{ + "allow_copy": 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, + "fields": [ + { + "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_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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Name of person or organization that this address belongs to.", + "fieldname": "address_title", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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, + "unique": 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_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\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, + "unique": 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_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, + "unique": 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_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, + "unique": 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": 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, + "unique": 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_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, + "unique": 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": 1, + "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, + "unique": 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": 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, + "unique": 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": 1, + "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, + "unique": 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_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, + "unique": 0, + "width": "50%" + }, + { + "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_list_view": 0, + "in_standard_filter": 0, + "label": "Email 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, + "unique": 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_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, + "unique": 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": 1, + "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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 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_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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-map-marker", + "idx": 5, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-13 05:01:15.084023", + "modified_by": "Administrator", + "module": "Geo", + "name": "Address", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "country, state", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/geo/doctype/address/address.py b/frappe/geo/doctype/address/address.py new file mode 100644 index 0000000000..2090dc3aee --- /dev/null +++ b/frappe/geo/doctype/address/address.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe + +from frappe import throw, _ +from frappe.utils import cstr + +from frappe.model.document import Document +from jinja2 import TemplateSyntaxError +from frappe.utils.user import is_website_user +from frappe.model.naming import make_autoname + +class Address(Document): + def __setup__(self): + self.flags.linked = False + + def autoname(self): + if not self.address_title: + if self.links: + self.address_title = self.links[0].link_name + + if self.address_title: + self.name = (cstr(self.address_title).strip() + "-" + cstr(self.address_type).strip()) + if frappe.db.exists("Address", self.name): + self.name = make_autoname(cstr(self.address_title).strip() + "-" + + cstr(self.address_type).strip() + "-.#") + else: + throw(_("Address Title is mandatory.")) + + def validate(self): + self.link_address() + self.validate_reference() + + def link_address(self): + """Link address based on owner""" + if not self.links and not self.is_your_company_address: + contact_name = frappe.db.get_value("Contact", {"email_id": self.owner}) + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + for link in contact.links: + self.append('links', dict(link_doctype=link.link_doctype, link_name=link.link_name)) + return True + + return False + + def validate_reference(self): + if self.is_your_company_address: + if not self.company: + frappe.throw(_("Company is mandatory, as it is your company address")) + if self.links: + self.links = [] + + def get_display(self): + return get_address_display(self.as_dict()) + + def has_link(self, doctype, name): + for link in self.links: + if link.link_doctype==doctype and link.link_name== name: + return True + + def has_common_link(self, doc): + reference_links = [(link.link_doctype, link.link_name) for link in doc.links] + for link in self.links: + if (link.link_doctype, link.link_name) in reference_links: + return True + + return False + +def get_default_address(doctype, name, sort_key='is_primary_address'): + '''Returns default Address name for the given doctype, name''' + out = frappe.db.sql('''select address.name + from + tabAddress address, `tabDynamic Link` dl + where + dl.link_doctype=%s and + dl.link_name=%s and + dl.parent = address.name and + dl.parenttype = "Address" + order by + address.`{0}` desc, name + limit 1'''.format(sort_key), (doctype, name)) + return out and out[0][0] or None + + +@frappe.whitelist() +def get_address_display(address_dict): + if not address_dict: + return + + if not isinstance(address_dict, dict): + address_dict = frappe.db.get_value("Address", address_dict, "*", as_dict=True) or {} + + name, template = get_address_templates(address_dict) + + try: + return frappe.render_template(template, address_dict) + except TemplateSyntaxError: + frappe.throw(_("There is an error in your Address Template {0}").format(name)) + + +def get_territory_from_address(address): + """Tries to match city, state and country of address to existing territory""" + if not address: + return + + if isinstance(address, basestring): + address = frappe.get_doc("Address", address) + + territory = None + for fieldname in ("city", "state", "country"): + territory = frappe.db.get_value("Territory", address.get(fieldname)) + if territory: + break + + return territory + +def get_list_context(context=None): + return { + "title": _("Addresses"), + "get_list": get_address_list, + "row_template": "templates/includes/address_row.html", + 'no_breadcrumbs': True, + } + +def get_address_list(doctype, txt, filters, limit_start, limit_page_length=20): + from frappe.www.list import get_list + user = frappe.session.user + ignore_permissions = False + if is_website_user(): + if not filters: filters = [] + filters.append(("Address", "owner", "=", user)) + ignore_permissions = True + + 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""" + contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) + if contact_name: + contact = frappe.get_doc('Contact', contact_name) + return contact.has_common_link(doc) + + lead_name = frappe.db.get_value("Lead", {"email_id": frappe.session.user}) + if lead_name: + return doc.has_link('Lead', lead_name) + + return False + +def get_address_templates(address): + result = frappe.db.get_value("Address Template", \ + {"country": address.get("country")}, ["name", "template"]) + + if not result: + result = frappe.db.get_value("Address Template", \ + {"is_default": 1}, ["name", "template"]) + + if not result: + frappe.throw(_("No default Address Template found. Please create a new one from Setup > Printing and Branding > Address Template.")) + else: + return result + +@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: + name, address_template = get_address_templates(address_as_dict) + return address_as_dict.get("name"), frappe.render_template(address_template, address_as_dict) + +def contact_query(doctype, txt, searchfield, start, page_len, filters): + from frappe.desk.reportview import get_match_cond + + return frappe.db.sql("""select + address.name, address.city, address.country + from + tabAddress as address, `tabDynamic Link` as dl + where + dl.parent = address.name and + dl.parenttype = 'Address' and + dl.link_doctype = %(link_doctype)s and + dl.link_name = %(link_name)s and + address.`{key}` like %(txt)s + {mcond} + 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)), + { + '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') + }) diff --git a/frappe/geo/doctype/address/test_address.py b/frappe/geo/doctype/address/test_address.py new file mode 100644 index 0000000000..2c067799f9 --- /dev/null +++ b/frappe/geo/doctype/address/test_address.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe, unittest +test_records = frappe.get_test_records('Address') + +from frappe.geo.doctype.address.address import get_address_display + +class TestAddress(unittest.TestCase): + def test_template_works(self): + address = frappe.get_list("Address")[0].name + display = get_address_display(frappe.get_doc("Address", address).as_dict()) + self.assertTrue(display) + + +test_dependencies = ["Address Template"] diff --git a/frappe/geo/doctype/address/test_records.json b/frappe/geo/doctype/address/test_records.json new file mode 100644 index 0000000000..a7bde9a814 --- /dev/null +++ b/frappe/geo/doctype/address/test_records.json @@ -0,0 +1,15 @@ +[ + { + "address_line1": "_Test Address Line 1", + "address_title": "_Test Address", + "address_type": "Office", + "city": "_Test City", + "state": "Test State", + "country": "India", + "customer": "_Test Customer", + "customer_name": "_Test Customer", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000" + } +] \ No newline at end of file diff --git a/frappe/geo/doctype/address_template/__init__.py b/frappe/geo/doctype/address_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/geo/doctype/address_template/address_template.js b/frappe/geo/doctype/address_template/address_template.js new file mode 100644 index 0000000000..db3c68c220 --- /dev/null +++ b/frappe/geo/doctype/address_template/address_template.js @@ -0,0 +1,16 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Address Template', { + refresh: function(frm) { + if(frm.is_new() && !frm.doc.template) { + // set default template via js so that it is translated + frappe.call({ + method: 'frappe.geo.doctype.address_template.address_template.get_default_address_template', + callback: function(r) { + frm.set_value('template', r.message); + } + }); + } + } +}); diff --git a/frappe/geo/doctype/address_template/address_template.json b/frappe/geo/doctype/address_template/address_template.json new file mode 100644 index 0000000000..8bc0c91367 --- /dev/null +++ b/frappe/geo/doctype/address_template/address_template.json @@ -0,0 +1,148 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "field:country", + "beta": 0, + "creation": "2014-06-05 02:22:36.029850", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "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_list_view": 1, + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "This format is used if country specific format is not found", + "fieldname": "is_default", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Is Default", + "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, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "description": "

    Default Template

    \n

    Uses Jinja Templating and all the fields of Address (including Custom Fields if any) will be available

    \n
    {{ address_line1 }}<br>\n{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}\n{{ city }}<br>\n{% if state %}{{ state }}<br>{% endif -%}\n{% if pincode %} PIN:  {{ pincode }}<br>{% endif -%}\n{{ country }}<br>\n{% if phone %}Phone: {{ phone }}<br>{% endif -%}\n{% if fax %}Fax: {{ fax }}<br>{% endif -%}\n{% if email_id %}Email: {{ email_id }}<br>{% endif -%}\n
    ", + "fieldname": "template", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Template", + "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, + "unique": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-map-marker", + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-13 05:11:37.499528", + "modified_by": "Administrator", + "module": "Geo", + "name": "Address Template", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 1, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/geo/doctype/address_template/address_template.py b/frappe/geo/doctype/address_template/address_template.py new file mode 100644 index 0000000000..9336f75027 --- /dev/null +++ b/frappe/geo/doctype/address_template/address_template.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.utils.jinja import validate_template +from frappe import _ + +class AddressTemplate(Document): + def validate(self): + if not self.template: + self.template = get_default_address_template() + + self.defaults = frappe.db.get_values("Address Template", {"is_default":1, "name":("!=", self.name)}) + if not self.is_default: + if not self.defaults: + self.is_default = 1 + frappe.msgprint(_("Setting this Address Template as default as there is no other default")) + + validate_template(self.template) + + def on_update(self): + if self.is_default and self.defaults: + for d in self.defaults: + frappe.db.set_value("Address Template", d[0], "is_default", 0) + + def on_trash(self): + if self.is_default: + frappe.throw(_("Default Address Template cannot be deleted")) + +@frappe.whitelist() +def get_default_address_template(): + '''Get default address template (translated)''' + return '''{{ address_line1 }}
    {% if address_line2 %}{{ address_line2 }}
    {% endif -%}\ +{{ city }}
    +{% if state %}{{ state }}
    {% endif -%} +{% if pincode %}{{ pincode }}
    {% endif -%} +{{ country }}
    +{% if phone %}'''+_('Phone')+''': {{ phone }}
    {% endif -%} +{% if fax %}'''+_('Fax')+''': {{ fax }}
    {% endif -%} +{% if email_id %}'''+_('Email')+''': {{ email_id }}
    {% endif -%}''' diff --git a/frappe/geo/doctype/address_template/test_address_template.py b/frappe/geo/doctype/address_template/test_address_template.py new file mode 100644 index 0000000000..8e300ac457 --- /dev/null +++ b/frappe/geo/doctype/address_template/test_address_template.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe, unittest +test_records = frappe.get_test_records('Address Template') + +class TestAddressTemplate(unittest.TestCase): + def test_default_is_unset(self): + a = frappe.get_doc("Address Template", "India") + a.is_default = 1 + a.save() + + b = frappe.get_doc("Address Template", "Brazil") + b.is_default = 1 + b.save() + + self.assertEqual(frappe.db.get_value("Address Template", "India", "is_default"), 0) + + def tearDown(self): + a = frappe.get_doc("Address Template", "India") + a.is_default = 1 + a.save() diff --git a/frappe/geo/doctype/address_template/test_records.json b/frappe/geo/doctype/address_template/test_records.json new file mode 100644 index 0000000000..412c9e745b --- /dev/null +++ b/frappe/geo/doctype/address_template/test_records.json @@ -0,0 +1,13 @@ +[ + { + "country": "India", + "is_default": 1, + "template": "{{ address_title }}
    \n{{ address_line1 }}
    \n{% if address_line2 %}{{ address_line2 }}
    {% endif %}\n{{ city }}
    \n{% if state %}{{ state }}
    {% endif %}\n{% if pincode %} PIN / ZIP: {{ pincode }}
    {% endif %}\n{{ country }}
    \n{% if phone %}Phone: {{ phone }}
    {% endif %}\n{% if fax %}Fax: {{ fax }}
    {% endif %}\n{% if email_id %}Email: {{ email_id }}
    {% endif %}\n" + }, + { + "country": "Brazil", + "is_default": 0, + "template": "{{ address_title }}
    \n{{ address_line1 }}
    \n{% if address_line2 %}{{ address_line2 }}
    {% endif %}\n{{ city }}
    \n{% if state %}{{ state }}
    {% endif %}\n{% if pincode %} PIN / ZIP: {{ pincode }}
    {% endif %}\n{{ country }}
    \n{% if phone %}Phone: {{ phone }}
    {% endif %}\n{% if fax %}Fax: {{ fax }}
    {% endif %}\n{% if email_id %}Email: {{ email_id }}
    {% endif %}\n" + } +] + diff --git a/frappe/hooks.py b/frappe/hooks.py index a79e377763..11feb9c0a8 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -91,6 +91,10 @@ has_permission = { "Communication": "frappe.core.doctype.communication.communication.has_permission" } +has_website_permission = { + "Address": "erpnext.utilities.doctype.address.address.has_website_permission" +} + standard_queries = { "User": "frappe.core.doctype.user.user.user_query" } diff --git a/frappe/patches.txt b/frappe/patches.txt index f686c4ea31..e991e2cbf4 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -147,12 +147,8 @@ frappe.patches.v7_0.cleanup_list_settings execute:frappe.db.set_default('language', '') frappe.patches.v7_1.refactor_integration_broker frappe.patches.v7_1.set_backup_limit -frappe.patches.v7_1.fix_email_sender -execute:frappe.db.sql("update tabCommunication SET docstatus = 0 where docstatus =2 or docstatus = 1") -execute:frappe.db.sql("update tabCommunication set communication_date = if(actualdate,actualdate,creation) where time(communication_date) = 0 or actualdate is not null") frappe.patches.v7_1.disabled_print_settings_for_custom_print_format frappe.patches.v7_2.set_doctype_engine frappe.patches.v7_2.merge_knowledge_base frappe.patches.v7_0.update_report_builder_json -frappe.patches.v7_2.match_emails_to_contacts frappe.patches.v7_2.set_in_standard_filter_property #1 \ No newline at end of file diff --git a/frappe/patches/v7_1/fix_email_sender.py b/frappe/patches/v7_1/fix_email_sender.py deleted file mode 100644 index edddea4bf9..0000000000 --- a/frappe/patches/v7_1/fix_email_sender.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import extract_email_id -from inbox.email_inbox.contact import match_email_to_contact -def execute(): - frappe.reload_doctype('Communication') - for c in frappe.db.sql("""select name,sender from tabCommunication where communication_type = 'Communication' and sender like '%<%>'""",as_dict=1): - frappe.db.set_value('Communication', c.name, 'sender',extract_email_id(c.sender) , update_modified=False) - communication = frappe.get_doc('Communication', c.name) - match_email_to_contact(communication) - - \ No newline at end of file diff --git a/frappe/patches/v7_2/match_emails_to_contacts.py b/frappe/patches/v7_2/match_emails_to_contacts.py deleted file mode 100644 index 68bca0d520..0000000000 --- a/frappe/patches/v7_2/match_emails_to_contacts.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import unicode_literals -import frappe -def execute(): - frappe.db.sql("update `tabContact` set email_id = lower(email_id)") - frappe.db.sql("update `tabCommunication` set sender = lower(sender),recipients = lower(recipients)") - - - origin_contact = frappe.db.sql("select name,email_id,supplier,supplier_name,customer,customer_name,user,organisation from `tabContact` where email_id <>''",as_dict=1) - origin_communication = frappe.db.sql("select name, sender,recipients,sent_or_received from `tabCommunication` where communication_type = 'Communication'",as_dict=1) - - for communication in origin_communication: - # format contacts - for comm in origin_contact: - if (communication.sender and communication.sent_or_received == "Received" and communication.sender.find(comm.email_id) > -1) \ - or (communication.recipients !=None and communication.sent_or_received == "Sent" and communication.recipients.find(comm.email_id) > -1): - if sum(1 for x in [comm.supplier, comm.customer, comm.user,comm.organisation] if x) > 1: - frappe.db.sql("""update `tabCommunication` - set timeline_doctype = %(timeline_doctype)s, - timeline_name = %(timeline_name)s, - timeline_label = %(timeline_label)s - where name = %(name)s""", { - "timeline_doctype": "Contact", - "timeline_name": comm.name, - "timeline_label": comm.name, - "name": communication.name - }) - - elif comm.supplier: - frappe.db.sql("""update `tabCommunication` - set timeline_doctype = %(timeline_doctype)s, - timeline_name = %(timeline_name)s, - timeline_label = %(timeline_label)s - where name = %(name)s""", { - "timeline_doctype": "Supplier", - "timeline_name": comm.supplier, - "timeline_label": comm.supplier_name, - "name": communication.name - }) - - elif comm.customer: - frappe.db.sql("""update `tabCommunication` - set timeline_doctype = %(timeline_doctype)s, - timeline_name = %(timeline_name)s, - timeline_label = %(timeline_label)s - where name = %(name)s""", { - "timeline_doctype": "Customer", - "timeline_name": comm.customer, - "timeline_label": comm.customer_name, - "name": communication.name - }) - elif comm.user: - frappe.db.sql("""update `tabCommunication` - set timeline_doctype = %(timeline_doctype)s, - timeline_name = %(timeline_name)s, - timeline_label = %(timeline_label)s - where name = %(name)s""", { - "timeline_doctype": "User", - "timeline_name": comm.user, - "timeline_label": comm.user, - "name": communication.name - }) - elif comm.organisation: - frappe.db.sql("""update `tabCommunication` - set timeline_doctype = %(timeline_doctype)s, - timeline_name = %(timeline_name)s, - timeline_label = %(timeline_label)s - where name = %(name)s""", { - "timeline_doctype": "Organisation", - "timeline_name": comm.organisation, - "timeline_label": comm.organisation, - "name": communication.name - }) \ No newline at end of file diff --git a/frappe/public/js/legacy/clientscriptAPI.js b/frappe/public/js/legacy/clientscriptAPI.js index 3d9bb1a7fb..3504420f03 100644 --- a/frappe/public/js/legacy/clientscriptAPI.js +++ b/frappe/public/js/legacy/clientscriptAPI.js @@ -248,7 +248,9 @@ _f.Frm.prototype.set_query = function(fieldname, opt1, opt2) { } else { // on parent table // set_query(fieldname, query) - this.fields_dict[fieldname].get_query = opt1; + if(this.fields_dict[fieldname]) { + this.fields_dict[fieldname].get_query = opt1; + } } }