diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 52c6483ab4..1cf7f4e6d8 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -8,11 +8,11 @@ context('List View', () => { cy.clear_cache(); }); it('enables "Actions" button', () => { - const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Print','Delete']; + const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Print', 'Delete']; cy.go_to_list('ToDo'); cy.get('.level-item.list-row-checkbox.hidden-xs').click({ multiple: true, force: true }); cy.get('.btn.btn-primary.btn-sm.dropdown-toggle').contains('Actions').should('be.visible').click(); - cy.get('.dropdown-menu li:visible').should('have.length', 6).each((el, index) => { + cy.get('.dropdown-menu li:visible').should('have.length', 7).each((el, index) => { cy.wrap(el).contains(actions[index]); }).then((elements) => { cy.server(); diff --git a/frappe/__init__.py b/frappe/__init__.py index d185962231..78e0cdb355 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if sys.version[0] == '2': reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.12' +__version__ = '12.0.13' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.json b/frappe/automation/doctype/assignment_rule/assignment_rule.json index 984ca9928a..799efefd04 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.json +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.json @@ -1,498 +1,142 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, "allow_rename": 1, "autoname": "Prompt", - "beta": 0, "creation": "2019-02-28 17:12:18.815830", - "custom": 0, "description": "Automatically Assign Documents to Users", - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "document_type", + "priority", + "disabled", + "column_break_4", + "description", + "assignment_rules_section", + "assign_condition", + "column_break_6", + "unassign_condition", + "section_break_10", + "close_condition", + "assign_to_users_section", + "rule", + "users", + "last_user" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "document_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Document Type", - "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Higher priority rule will be applied first", - "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Priority", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Priority" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Disabled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Automatic Assignment", "description": "Example: {{ subject }}", - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Description", - "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assignment_rules_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assignment Rules", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Assignment Rules" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Simple Python Expression, Example: status == 'Open' and type == 'Bug'", - "fetch_if_empty": 0, "fieldname": "assign_condition", "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Assign Condition", - "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Simple Python Expression, Example: Status in (\"Closed\", \"Cancelled\")", - "fetch_if_empty": 0, "fieldname": "unassign_condition", "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unassign Condition", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Unassign Condition" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assign_to_users_section", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assign To Users", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Assign To Users" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "rule", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Rule", - "length": 0, - "no_copy": 0, "options": "Round Robin\nLoad Balancing", - "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, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "users", "fieldtype": "Table MultiSelect", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Users", - "length": 0, - "no_copy": 0, "options": "Assignment Rule 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "last_user", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Last User", - "length": 0, - "no_copy": 0, "options": "User", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "description": "Simple Python Expression, Example: Status in (\"Invalid\")", + "fieldname": "close_condition", + "fieldtype": "Code", + "label": "Close Condition" } ], - "has_web_view": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-16 17:46:04.890120", + "modified": "2019-09-10 14:45:53.657667", "modified_by": "Administrator", "module": "Automation", "name": "Assignment Rule", - "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 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, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index a6b56b6466..f4c4a25830 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -23,12 +23,18 @@ class AssignmentRule(Document): return False + def apply_close(self, doc, assignments): + if (self.close_assignments and + self.name in [d.assignment_rule for d in assignments]): + return self.close_assignments(doc) + + return False + def apply_assign(self, doc): if self.safe_eval('assign_condition', doc): self.do_assignment(doc) return True - def do_assignment(self, doc): # clear existing assignment, to reassign assign_to.clear(doc.get('doctype'), doc.get('name')) @@ -52,6 +58,11 @@ class AssignmentRule(Document): if self.safe_eval('unassign_condition', doc): return assign_to.clear(doc.get('doctype'), doc.get('name')) + def close_assignments(self, doc): + '''Close assignments''' + if self.safe_eval('close_condition', doc): + return assign_to.close_all_assignments(doc.get('doctype'), doc.get('name')) + def get_user(self): ''' Get the next user for assignment @@ -98,23 +109,54 @@ class AssignmentRule(Document): def safe_eval(self, fieldname, doc): try: - return frappe.safe_eval(self.get(fieldname), None, doc) + if self.get(fieldname): + return frappe.safe_eval(self.get(fieldname), None, doc) except Exception as e: # when assignment fails, don't block the document as it may be # a part of the email pulling frappe.msgprint(frappe._('Auto assignment failed: {0}').format(str(e)), indicator = 'orange') + return False + def get_assignments(doc): return frappe.get_all('ToDo', fields = ['name', 'assignment_rule'], filters = dict( reference_type = doc.get('doctype'), reference_name = doc.get('name'), - status = 'Open' + status = ('!=', 'Cancelled') ), limit = 5) -def apply(doc, method): +@frappe.whitelist() +def bulk_apply(doctype, docnames): + import json + docnames = json.loads(docnames) + + background = len(docnames) > 5 + for name in docnames: + if background: + frappe.enqueue('frappe.automation.doctype.assignment_rule.assignment_rule.apply', doc=None, doctype=doctype, name=name) + else: + apply(None, doctype=doctype, name=name) + +def reopen_closed_assignment(doc): + todo = frappe.db.exists('ToDo', dict( + reference_type = doc.doctype, + reference_name = doc.name, + status = 'Closed' + )) + if not todo: + return False + todo = frappe.get_doc("ToDo", todo) + todo.status = 'Open' + todo.save(ignore_permissions=True) + return True + +def apply(doc, method=None, doctype=None, name=None): if frappe.flags.in_patch or frappe.flags.in_install: return + if not doc and doctype and name: + doc = frappe.get_doc(doctype, name) + assignment_rules = frappe.cache_manager.get_doctype_map('Assignment Rule', doc.doctype, dict( document_type = doc.doctype, disabled = 0), order_by = 'priority desc') @@ -130,19 +172,38 @@ def apply(doc, method): doc = doc.as_dict() assignments = get_assignments(doc) - clear = True + clear = True # are all assignments cleared + new_apply = False # are new assignments applied + if assignments: # first unassign + # use case, there are separate groups to be assigned for say L1 and L2, + # so when the value switches from L1 to L2, L1 team must be unassigned, then L2 can be assigned. clear = False for assignment_rule in assignment_rule_docs: clear = assignment_rule.apply_unassign(doc, assignments) - if clear: break + if clear: + break # apply rule only if there are no existing assignments if clear: for assignment_rule in assignment_rule_docs: - if assignment_rule.apply_assign(doc): break + new_apply = assignment_rule.apply_assign(doc) + if new_apply: + break + + # apply close rule only if assignments exists + assignments = get_assignments(doc) + if assignments: + for assignment_rule in assignment_rule_docs: + if not new_apply: + reopen = reopen_closed_assignment(doc) + if reopen: + break + close = assignment_rule.apply_close(doc, assignments) + if close: + break def get_assignment_rules(): - return [d.document_type for d in frappe.db.get_all('Assignment Rule', fields=['document_type'], filters=dict(disabled = 0))] \ No newline at end of file + return [d.document_type for d in frappe.db.get_all('Assignment Rule', fields=['document_type'], filters=dict(disabled = 0))] diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index 098b30cbae..3b1e1a76fa 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -91,22 +91,46 @@ class TestAutoAssign(unittest.TestCase): note = make_note(dict(public=1)) # check if auto assigned to first user - self.assertEqual(frappe.db.get_value('ToDo', dict( + todo = frappe.get_list('ToDo', dict( reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test@example.com') + ))[0] + + todo = frappe.get_doc('ToDo', todo['name']) + self.assertEqual(todo.owner, 'test@example.com') # test auto unassign note.public = 0 note.save() - # check if cleared - self.assertEqual(frappe.db.get_value('ToDo', dict( + todo.load_from_db() + + # check if todo is cancelled + self.assertEqual(todo.status, 'Cancelled') + + def test_close_assignment(self): + note = make_note(dict(public=1, content="valid")) + + # check if auto assigned + todo = frappe.get_list('ToDo', dict( reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), None) + ))[0] + + todo = frappe.get_doc('ToDo', todo['name']) + self.assertEqual(todo.owner, 'test@example.com') + + note.content="Closed" + note.save() + + todo.load_from_db() + + # check if todo is closed + self.assertEqual(todo.status, 'Closed') + # check if closed todo retained assignment + self.assertEqual(todo.owner, 'test@example.com') def check_multiple_rules(self): note = make_note(dict(public=1, notify_on_login=1)) @@ -131,6 +155,7 @@ def get_assignment_rule(): document_type = 'Note', assign_condition = 'public == 1', unassign_condition = 'public == 0 or notify_on_login == 1', + close_condition = '"Closed" in content', rule = 'Round Robin', users = [ dict(user = 'test@example.com'), diff --git a/frappe/contacts/doctype/contact/contact.js b/frappe/contacts/doctype/contact/contact.js index c6ad8cc269..7bbbbb1564 100644 --- a/frappe/contacts/doctype/contact/contact.js +++ b/frappe/contacts/doctype/contact/contact.js @@ -41,6 +41,24 @@ frappe.ui.form.on("Contact", { } }); frm.refresh_field("links"); + + if (frm.doc.links.length > 0) { + frappe.call({ + method: "frappe.contacts.doctype.contact.contact.address_query", + args: {links: frm.doc.links}, + callback: function(r) { + if (r && r.message) { + frm.set_query("address", function () { + return { + filters: { + name: ["in", r.message], + } + } + }); + } + } + }); + } }, validate: function(frm) { // clear linked customer / supplier / sales partner on saving... @@ -61,6 +79,15 @@ frappe.ui.form.on("Contact", { } } ]); + }, + sync_with_google_contacts: function(frm) { + if (frm.doc.sync_with_google_contacts) { + frappe.db.get_value("Google Contacts", {"email_id": frappe.session.user}, "name", (r) => { + if (r && r.name) { + frm.set_value("google_contacts", r.name); + } + }) + } } }); diff --git a/frappe/contacts/doctype/contact/contact.json b/frappe/contacts/doctype/contact/contact.json index f700411e80..85986dd9d5 100644 --- a/frappe/contacts/doctype/contact/contact.json +++ b/frappe/contacts/doctype/contact/contact.json @@ -14,14 +14,21 @@ "email_id", "user", "address", + "sync_with_google_contacts", "cb00", "status", "salutation", "designation", "gender", "phone", + "company_name", "image", "sb_00", + "google_contacts", + "google_contacts_id", + "cb_00", + "pulled_from_google_contacts", + "sb_01", "email_ids", "phone_nos", "contact_details", @@ -29,10 +36,7 @@ "links", "more_info", "department", - "unsubscribed", - "column_break_17", - "source", - "google_contacts_description" + "unsubscribed" ], "fields": [ { @@ -155,36 +159,23 @@ "fieldtype": "Data", "label": "Designation" }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "unsubscribed", "fieldtype": "Check", "label": "Unsubscribed" }, - { - "fieldname": "source", - "fieldtype": "Data", - "label": "Source" - }, { "fieldname": "middle_name", "fieldtype": "Data", "label": "Middle Name" }, { - "depends_on": "eval:doc.source==\"Google Contacts\"", - "fieldname": "google_contacts_description", - "fieldtype": "Small Text", - "label": "Google Contacts Description" - }, - { + "collapsible": 1, + "depends_on": "eval:doc.sync_with_google_contacts || doc.pulled_from_google_contacts", "fieldname": "sb_00", "fieldtype": "Section Break", - "label": "Contact Details" + "label": "Google Contacts" }, { "fieldname": "email_ids", @@ -203,13 +194,52 @@ "fieldtype": "Table", "label": "Phone Nos", "options": "Contact Phone" + }, + { + "default": "0", + "fieldname": "pulled_from_google_contacts", + "fieldtype": "Check", + "label": "Pulled from Google Contacts", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "sync_with_google_contacts", + "fieldtype": "Check", + "label": "Sync with Google Contacts" + }, + { + "fieldname": "google_contacts", + "fieldtype": "Link", + "label": "Google Contacts", + "options": "Google Contacts" + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, + { + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "Contact Details" + }, + { + "fieldname": "google_contacts_id", + "fieldtype": "Data", + "label": "Google Contacts Id", + "read_only": 1 + }, + { + "fieldname": "company_name", + "fieldtype": "Data", + "label": "Company Name" } ], "icon": "fa fa-user", "idx": 1, "image_field": "image", - "modified": "2019-08-09 10:23:00.486673", - "modified_by": "Administrator", + "modified": "2019-09-13 15:50:38.999884", + "modified_by": "himanshu@erpnext.com", "module": "Contacts", "name": "Contact", "name_case": "Title Case", diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index 085452988a..0b1231e511 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -42,6 +42,9 @@ class Contact(Document): if self.email_id and not self.image: self.image = has_gravatar(self.email_id) + if self.get("sync_with_google_contacts") and not self.get("google_contacts"): + frappe.throw(_("Select Google Contacts to which contact should be synced.")) + deduplicate_dynamic_links(self) def set_user(self): @@ -195,6 +198,32 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters): 'link_doctype': link_doctype }) +@frappe.whitelist() +def address_query(links): + import json + + links = [{"link_doctype": d.get("link_doctype"), "link_name": d.get("link_name")} for d in json.loads(links)] + result = [] + + for link in links: + if not frappe.has_permission(doctype=link.get("link_doctype"), ptype="read", doc=link.get("link_name")): + continue + + res = frappe.db.sql(""" + SELECT `tabAddress`.name + FROM `tabAddress`, `tabDynamic Link` + WHERE `tabDynamic Link`.parenttype='Address' + AND `tabDynamic Link`.parent=`tabAddress`.name + AND `tabDynamic Link`.link_doctype = %(link_doctype)s + AND `tabDynamic Link`.link_name = %(link_name)s + """, { + "link_doctype": link.get("link_doctype"), + "link_name": link.get("link_name"), + }, as_dict=True) + + result.extend([l.name for l in res]) + + return result def get_contact_with_phone_number(number): if not number: return diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index e808cd3053..eb4424db30 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -583,7 +583,7 @@ class DocType(Document): create_custom_field(self.name, df) def validate_nestedset(self): - if not self.is_tree: + if not self.get('is_tree'): return self.add_nestedset_fields() # set field as mandatory @@ -692,6 +692,13 @@ def validate_fields(meta): validate_column_name(fieldname) + def check_invalid_fieldnames(docname, fieldname): + invalid_fields = ('doctype',) + if fieldname in invalid_fields: + frappe.throw(_("{0}: Fieldname cannot be one of {1}") + .format(docname, ", ".join([frappe.bold(d) for d in invalid_fields]))) + + def check_unique_fieldname(docname, fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) if len(duplicates) > 1: @@ -949,6 +956,7 @@ def validate_fields(meta): d.fieldname = d.fieldname.lower() check_illegal_characters(d.fieldname) + check_invalid_fieldnames(meta.get("name"), d.fieldname) check_unique_fieldname(meta.get("name"), d.fieldname) check_fieldname_length(d.fieldname) check_illegal_mandatory(meta.get("name"), d) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index d334dc5346..3c89519faf 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -201,14 +201,16 @@ class File(NestedSet): duplicate_file = frappe.db.get_value('File', filters, ['name', 'file_url'], as_dict=1) if duplicate_file: - # if it is attached to a document then throw DuplicateEntryError - if self.attached_to_doctype and self.attached_to_name: - self.duplicate_entry = duplicate_file.name - frappe.throw(_("Same file has already been attached to the record"), - frappe.DuplicateEntryError) - # else just use the url, to avoid uploading a duplicate - else: - self.file_url = duplicate_file.file_url + duplicate_file_doc = frappe.get_cached_doc('File', duplicate_file.name) + if duplicate_file_doc.exists_on_disk(): + # if it is attached to a document then throw DuplicateEntryError + if self.attached_to_doctype and self.attached_to_name: + self.duplicate_entry = duplicate_file.name + frappe.throw(_("Same file has already been attached to the record"), + frappe.DuplicateEntryError) + # else just use the url, to avoid uploading a duplicate + else: + self.file_url = duplicate_file.file_url def validate_file_name(self): if not self.file_name and self.file_url: @@ -332,6 +334,9 @@ class File(NestedSet): data = frappe.db.get_value("File", self.file_data_name, ["file_name", "file_url"], as_dict=True) return data.file_url or data.file_name + def exists_on_disk(self): + exists = os.path.exists(self.get_full_path()) + return exists def upload(self): # get record details @@ -495,19 +500,21 @@ class File(NestedSet): self.content_hash = get_content_hash(self.content) self.content_type = mimetypes.guess_type(self.file_name)[0] - _file = False + duplicate_file = None # check if a file exists with the same content hash and is also in the same folder (public or private) if not ignore_existing_file_check: - _file = frappe.get_value("File", { + duplicate_file = frappe.get_value("File", { "content_hash": self.content_hash, "is_private": self.is_private }, - ["file_url"]) + ["file_url", "name"], as_dict=True) - if _file: - self.file_url = _file - file_exists = True + if duplicate_file: + file_doc = frappe.get_cached_doc('File', duplicate_file.name) + if file_doc.exists_on_disk(): + self.file_url = duplicate_file.file_url + file_exists = True if os.path.exists(encode(get_files_path(self.file_name, is_private=self.is_private))): self.file_name = get_file_name(self.file_name, self.content_hash[-6:]) diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py index d488ccd64a..10cb7b97ac 100644 --- a/frappe/core/page/background_jobs/background_jobs.py +++ b/frappe/core/page/background_jobs/background_jobs.py @@ -6,7 +6,7 @@ import frappe from rq import Queue, Worker from frappe.utils.background_jobs import get_redis_conn -from frappe.utils import format_datetime, cint +from frappe.utils import format_datetime, cint, convert_utc_to_user_timezone from frappe.utils.scheduler import is_scheduler_inactive from frappe import _ @@ -30,7 +30,7 @@ def get_info(show_failed=False): 'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \ or str(j.kwargs.get('job_name')), 'status': j.status, 'queue': name, - 'creation': format_datetime(j.created_at), + 'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)), 'color': colors[j.status] }) if j.exc_info: diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 0f31939eb8..9b60ea2b11 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -1,1432 +1,389 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, - "beta": 0, "creation": "2013-01-10 16:34:01", - "custom": 0, "description": "Adds a custom field to a DocType", - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "dt", + "label", + "label_help", + "fieldname", + "insert_after", + "length", + "column_break_6", + "fieldtype", + "precision", + "options", + "fetch_from", + "fetch_if_empty", + "options_help", + "section_break_11", + "collapsible", + "collapsible_depends_on", + "default", + "depends_on", + "description", + "permlevel", + "width", + "columns", + "properties", + "reqd", + "unique", + "read_only", + "ignore_user_permissions", + "hidden", + "print_hide", + "print_hide_if_no_value", + "print_width", + "no_copy", + "allow_on_submit", + "in_list_view", + "in_standard_filter", + "in_global_search", + "bold", + "report_hide", + "search_index", + "ignore_xss_filter", + "translatable" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "dt", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Document", - "length": 0, - "no_copy": 0, "oldfieldname": "dt", "oldfieldtype": "Link", "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "label", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Label", - "length": 0, "no_copy": 1, "oldfieldname": "label", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "label_help", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Label Help", - "length": 0, - "no_copy": 0, - "oldfieldtype": "HTML", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "HTML" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "fieldname", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Fieldname", - "length": 0, "no_copy": 1, "oldfieldname": "fieldname", "oldfieldtype": "Data", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "description": "Select the label after which you want to insert new field.", - "fetch_if_empty": 0, "fieldname": "insert_after", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Insert After", - "length": 0, "no_copy": 1, "oldfieldname": "insert_after", - "oldfieldtype": "Select", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Select" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_6", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, "default": "Data", - "fetch_if_empty": 0, "fieldname": "fieldtype", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Field Type", - "length": 0, - "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)", "description": "Set non-standard precision for a Float or Currency field", - "fetch_if_empty": 0, "fieldname": "precision", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Precision", - "length": 0, - "no_copy": 0, - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "options", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Options", - "length": 0, - "no_copy": 0, "oldfieldname": "options", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Text" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "fetch_from", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Fetch From", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Fetch From" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.", - "fetch_if_empty": 0, "fieldname": "fetch_if_empty", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Fetch If Empty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Fetch If Empty" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "options_help", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Options Help", - "length": 0, - "no_copy": 0, - "oldfieldtype": "HTML", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "HTML" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_11", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fetch_if_empty": 0, "fieldname": "collapsible", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Collapsible", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Collapsible" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.fieldtype==\"Section Break\"", - "fetch_if_empty": 0, "fieldname": "collapsible_depends_on", "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Collapsible Depends On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Collapsible Depends On" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default", "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Value", - "length": 0, - "no_copy": 0, "oldfieldname": "default", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Text" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "depends_on", "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Depends On", - "length": 255, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "length": 255 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Field Description", - "length": 0, - "no_copy": 0, "oldfieldname": "description", "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "300px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "300px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, "fieldname": "permlevel", "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Permission Level", - "length": 0, - "no_copy": 0, "oldfieldname": "permlevel", - "oldfieldtype": "Int", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Int" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "width", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Width", - "length": 0, - "no_copy": 0, "oldfieldname": "width", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)", - "fetch_if_empty": 0, "fieldname": "columns", "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Columns", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Columns" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "properties", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "50%", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "reqd", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Is Mandatory Field", - "length": 0, - "no_copy": 0, "oldfieldname": "reqd", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Check" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "unique", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unique", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Unique" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "read_only", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Read Only", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Read Only" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "depends_on": "eval:doc.fieldtype===\"Link\"", - "fetch_if_empty": 0, "fieldname": "ignore_user_permissions", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ignore User Permissions", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Ignore User Permissions" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "hidden", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hidden", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Hidden" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "print_hide", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Print Hide", - "length": 0, - "no_copy": 0, "oldfieldname": "print_hide", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Check" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1", - "fetch_if_empty": 0, "fieldname": "print_hide_if_no_value", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Hide If No Value", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Print Hide If No Value" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "print_width", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Print Width", - "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, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "no_copy", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "No Copy", - "length": 0, - "no_copy": 0, "oldfieldname": "no_copy", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Check" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "allow_on_submit", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Allow on Submit", - "length": 0, - "no_copy": 0, "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Check" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "in_list_view", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "In List View", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "In List View" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "in_standard_filter", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "In Standard Filter", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "In Standard Filter" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)", - "fetch_if_empty": 0, "fieldname": "in_global_search", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "In Global Search", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "In Global Search" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "bold", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bold", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Bold" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "report_hide", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Report Hide", - "length": 0, - "no_copy": 0, "oldfieldname": "report_hide", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Check" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "search_index", "fieldtype": "Check", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Index", - "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, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field", - "fetch_if_empty": 0, "fieldname": "ignore_xss_filter", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ignore XSS Filter", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Ignore XSS Filter" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fetch_if_empty": 0, "fieldname": "translatable", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Translatable", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Translatable" + }, + { + "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)", + "fieldname": "length", + "fieldtype": "Int", + "label": "Length" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-glass", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-03-18 18:03:38.155276", + "modified": "2019-09-11 12:57:19.268934", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Administrator", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "dt,label,fieldtype,options", - "show_name_in_global_search": 0, + "sort_field": "modified", "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 356a571adc..21679c5bc7 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -62,9 +62,7 @@ class CustomField(Document): # update the schema if not frappe.db.get_value('DocType', self.dt, 'issingle'): - if (self.fieldname not in frappe.db.get_table_columns(self.dt) - or getattr(self, "_old_fieldtype", None) != self.fieldtype): - frappe.db.updatedb(self.dt) + frappe.db.updatedb(self.dt) def on_trash(self): # delete property setter entries diff --git a/frappe/database/postgres/setup_db.py b/frappe/database/postgres/setup_db.py index 79c7c3a304..01a97178f9 100644 --- a/frappe/database/postgres/setup_db.py +++ b/frappe/database/postgres/setup_db.py @@ -17,7 +17,7 @@ def setup_database(force, source_sql, verbose): subprocess_env['PGPASSWORD'] = str(frappe.conf.db_password) # bootstrap db subprocess.check_output([ - 'psql', frappe.conf.db_name, '-h', frappe.conf.db_host, '-U', + 'psql', frappe.conf.db_name, '-h', frappe.conf.db_host or 'localhost', '-U', frappe.conf.db_name, '-f', os.path.join(os.path.dirname(__file__), 'framework_postgres.sql') ], env=subprocess_env) diff --git a/frappe/database/schema.py b/frappe/database/schema.py index d7766d15cd..1663eed95f 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -351,5 +351,3 @@ def add_column(doctype, column_name, fieldtype, precision=None): frappe.db.commit() frappe.db.sql("alter table `tab%s` add column %s %s" % (doctype, column_name, get_definition(fieldtype, precision))) - - diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index a0343e9f69..9e0598b128 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -1,687 +1,193 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "hash", - "beta": 0, "creation": "2012-07-03 13:30:35", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "description_and_status", + "status", + "priority", + "column_break_2", + "color", + "date", + "owner", + "description_section", + "description", + "section_break_6", + "reference_type", + "reference_name", + "column_break_10", + "role", + "assigned_by", + "assigned_by_full_name", + "sender", + "assignment_rule" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description_and_status", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Open", - "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "length": 0, - "no_copy": 0, - "options": "Open\nClosed", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Open\nClosed\nCancelled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Medium", - "fetch_if_empty": 0, "fieldname": "priority", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Priority", - "length": 0, - "no_copy": 0, "oldfieldname": "priority", "oldfieldtype": "Data", - "options": "High\nMedium\nLow", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "High\nMedium\nLow" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "color", "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Color" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Due Date", - "length": 0, - "no_copy": 0, "oldfieldname": "date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "oldfieldtype": "Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "owner", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Allocated To", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Description", - "length": 0, - "no_copy": 0, "oldfieldname": "description", "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "300px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "300px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Reference" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "reference_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Reference Type", - "length": 0, - "no_copy": 0, "oldfieldname": "reference_type", "oldfieldtype": "Data", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "DocType" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "reference_name", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reference Name", - "length": 0, - "no_copy": 0, "oldfieldname": "reference_name", "oldfieldtype": "Data", - "options": "reference_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "reference_type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "role", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Role", - "length": 0, - "no_copy": 0, "oldfieldname": "role", "oldfieldtype": "Link", - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Role" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assigned_by", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Assigned By", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "assigned_by.full_name", - "fetch_if_empty": 0, "fieldname": "assigned_by_full_name", "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Assigned By Full Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Assigned By Full Name" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sender", "fieldtype": "Data", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sender", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Sender" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "assignment_rule", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Assignment Rule", - "length": 0, - "no_copy": 0, "options": "Assignment Rule", - "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, - "translatable": 0, - "unique": 0 + "read_only": 1 } ], - "has_web_view": 0, - "hide_toolbar": 0, "icon": "fa fa-check", "idx": 2, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-24 15:45:23.290491", + "modified": "2019-09-10 14:34:59.161750", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "All", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "System Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, "search_fields": "description, reference_type, reference_name", - "show_name_in_global_search": 0, + "sort_field": "modified", "sort_order": "DESC", "title_field": "description", "track_changes": 1, - "track_seen": 1, - "track_views": 0 -} + "track_seen": 1 +} \ No newline at end of file diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 38856d5068..959eee7e60 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -20,7 +20,7 @@ def get(args=None): return frappe.get_all('ToDo', fields = ['owner', 'description'], filters = dict( reference_type = args.get('doctype'), reference_name = args.get('name'), - status = 'Open' + status = ('!=', 'Cancelled') ), limit = 5) @frappe.whitelist() @@ -48,9 +48,6 @@ def add(args=None): else: from frappe.utils import nowdate - # if args.get("re_assign"): - # remove_from_todo_if_already_assigned(args['doctype'], args['name']) - if not args.get('description'): args['description'] = _('Assignment for {0} {1}'.format(args['doctype'], args['name'])) @@ -100,20 +97,29 @@ def add_multiple(args=None): args.update({"name": docname}) add(args) -def remove_from_todo_if_already_assigned(doctype, docname): - owner = frappe.db.get_value("ToDo", {"reference_type": doctype, "reference_name": docname, - "status":"Open"}, "owner") - if owner: - remove(doctype, docname, owner) +def close_all_assignments(doctype, name): + assignments = frappe.db.get_all('ToDo', fields=['owner'], filters = + dict(reference_type = doctype, reference_name = name, status=('!=', 'Cancelled'))) + if not assignments: + return False + + for assign_to in assignments: + set_status(doctype, name, assign_to.owner, status="Closed") + + return True @frappe.whitelist() def remove(doctype, name, assign_to): + return set_status(doctype, name, assign_to, status="Cancelled") + +def set_status(doctype, name, assign_to, status="Cancelled"): """remove from todo""" try: - todo = frappe.db.get_value("ToDo", {"reference_type":doctype, "reference_name":name, "owner":assign_to, "status":"Open"}) + todo = frappe.db.get_value("ToDo", {"reference_type":doctype, + "reference_name":name, "owner":assign_to, "status": ('!=', status)}) if todo: todo = frappe.get_doc("ToDo", todo) - todo.status = "Closed" + todo.status = status todo.save(ignore_permissions=True) notify_assignment(todo.assigned_by, todo.owner, todo.reference_type, todo.reference_name) @@ -121,7 +127,7 @@ def remove(doctype, name, assign_to): pass # clear assigned_to if field exists - if frappe.get_meta(doctype).get_field("assigned_to"): + if frappe.get_meta(doctype).get_field("assigned_to") and status=="Cancelled": frappe.db.set_value(doctype, name, "assigned_to", None) return get({"doctype": doctype, "name": name}) @@ -136,7 +142,7 @@ def clear(doctype, name): return False for assign_to in assignments: - remove(doctype, name, assign_to.owner) + set_status(doctype, name, assign_to.owner, "Cancelled") return True diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index e790bf6d06..8c7082401a 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -217,12 +217,14 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields= return communications def get_assignments(dt, dn): - cl = frappe.db.sql("""select `name`, owner, description from `tabToDo` - where reference_type=%(doctype)s and reference_name=%(name)s and status='Open' - order by modified desc limit 5""", { - "doctype": dt, - "name": dn - }, as_dict=True) + cl = frappe.get_all("ToDo", + fields=['name', 'owner', 'description', 'status'], + limit= 5, + filters={ + 'reference_type': dt, + 'reference_name': dn, + 'status': ('!=', 'Cancelled'), + }) return cl diff --git a/frappe/desk/page/user_profile/user_profile.js b/frappe/desk/page/user_profile/user_profile.js index 110831963d..80b5ecc797 100644 --- a/frappe/desk/page/user_profile/user_profile.js +++ b/frappe/desk/page/user_profile/user_profile.js @@ -179,6 +179,7 @@ class UserProfile { labels: chart.labels, datasets: chart.datasets }, + truncateLegends: 1, barOptions: { height: 11, depth: 1 diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index c585dd023c..e3e408eeb1 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -52,6 +52,8 @@ def get_form_params(): key = field.split(" as ")[0] if key.startswith('count('): continue + if key.startswith('sum('): continue + if key.startswith('avg('): continue if "." in key: parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`") diff --git a/frappe/hooks.py b/frappe/hooks.py index eb0d5bbe77..d2e8570644 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -136,6 +136,15 @@ doc_events = { "on_change": [ "frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points" ], + }, + "Event": { + "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_event_in_google_calendar", + "on_update": "frappe.integrations.doctype.google_calendar.google_calendar.update_event_in_google_calendar", + "on_trash": "frappe.integrations.doctype.google_calendar.google_calendar.delete_event_from_google_calendar", + }, + "Contact": { + "after_insert": "frappe.integrations.doctype.google_contacts.google_contacts.insert_contacts_to_google_contacts", + "on_update": "frappe.integrations.doctype.google_contacts.google_contacts.update_contacts_to_google_contacts", } } diff --git a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py b/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py deleted file mode 100644 index 20cd303e9c..0000000000 --- a/frappe/integrations/data_migration_mapping/gcalendar_to_event/__init__.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe -from datetime import datetime, timedelta -from dateutil.parser import parse -from pytz import timezone -from frappe.utils import add_days - - -def pre_process(events): - if events["status"] == "cancelled": - if frappe.db.exists("Event", dict(gcalendar_sync_id=events["id"])): - e = frappe.get_doc("Event", dict(gcalendar_sync_id=events["id"])) - frappe.delete_doc("Event", e.name) - return {} - - elif events["status"] == "confirmed": - if 'date' in events["start"]: - datevar = 'date' - start_dt = parse(events["start"]['date']) - end_dt = add_days(parse(events["end"]['date']), -1) - elif 'dateTime' in events["start"]: - datevar = 'dateTime' - start_dt = parse(events["start"]['dateTime']) - end_dt = parse(events["end"]['dateTime']) - - if start_dt.tzinfo is None or start_dt.tzinfo.utcoffset(start_dt) is None: - if "timeZone" in events["start"]: - event_tz = events["start"]["timeZone"] - else: - event_tz = events["calendar_tz"] - start_dt = timezone(event_tz).localize(start_dt) - - if end_dt.tzinfo is None or end_dt.tzinfo.utcoffset(end_dt) is None: - if "timeZone" in events["end"]: - event_tz = events["end"]["timeZone"] - else: - event_tz = events["calendar_tz"] - end_dt = timezone(event_tz).localize(end_dt) - - default_tz = frappe.db.get_value("System Settings", None, "time_zone") - - event = { - 'id': events["id"], - 'summary': events["summary"], - 'start_datetime': start_dt.astimezone(timezone(default_tz)).strftime('%Y-%m-%d %H:%M:%S'), - 'end_datetime': end_dt.astimezone(timezone(default_tz)).strftime('%Y-%m-%d %H:%M:%S'), - 'account': events['account'] - } - - if "recurrence" in events: - recurrence = get_recurrence_event_fields_value(events['recurrence'][0], events["start"][datevar]) - - event.update(recurrence) - - if 'description' in events: - event.update({'description': events["description"]}) - else: - event.update({'description': ""}) - - if datevar == 'date': - event.update({'all_day': 1}) - - return event - - -def get_recurrence_event_fields_value(recur_rule, starts_on): - repeat_on = "" - repeat_till = "" - repeat_days = {} - # get recurrence rule from string - for _str in recur_rule.split(";"): - if "RRULE:FREQ" in _str: - repeat_every = _str.split("=")[1] - if repeat_every == "DAILY": repeat_on = "Daily" - elif repeat_every == "WEEKLY": repeat_on = "Weekly" - elif repeat_every == "MONTHLY": repeat_on = "Monthly" - else: repeat_on = "Yearly" - elif "UNTIL" in _str: - # get repeat till - date = parse(_str.split("=")[1]) - repeat_till = get_repeat_till_date(date) - elif "COUNT" in _str: - # get repeat till - date = parse(starts_on) - repeat_till = get_repeat_till_date(date, count=_str.split("=")[1], repeat_on=repeat_on) - elif "BYDAY" in _str: - days = _str.split("=")[1] - repeat_days.update({ - "sunday": 1 if "SU" in days else 0, - "monday": 1 if "MO" in days else 0, - "tuesday": 1 if "TU" in days else 0, - "wednesday": 1 if "WD" in days else 0, - "thursday": 1 if "TH" in days else 0, - "friday": 1 if "FR" in days else 0, - "saturday": 1 if "SA" in days else 0, - }) - repeat_on = "Weekly" - - recurrence = { - "repeat_on": repeat_on, - "repeat_till": repeat_till, - "repeat_this_event": 1 - } - - if repeat_days: - recurrence.update(repeat_days) - - return recurrence - - -def get_repeat_till_date(date, count=None, repeat_on=None): - if count: - if repeat_on == "Daily": - # add days - date = date + timedelta(days=int(count)) - elif repeat_on == "Weekly": - # add weeks - date = date + timedelta(weeks=int(count)) - elif repeat_on == "Monthly": - # add months - date = add_months(date, int(count)) - elif repeat_on == "Yearly": - # add years - date = add_months(date, int(count) * 12) - else: - # set default value - date = add_months(date, int(count)) - - return date.strftime("%Y-%m-%d") - -def add_months(date, count): - import calendar - - month = date.month - 1 + count - year = date.year + month / 12 - month = month % 12 + 1 - day = min(date.day,calendar.monthrange(year,month)[1]) - return datetime(year,month,day) diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.json b/frappe/integrations/doctype/google_contacts/google_contacts.json index 42fb9e68c8..1089c6b635 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.json +++ b/frappe/integrations/doctype/google_contacts/google_contacts.json @@ -11,7 +11,12 @@ "cb_00", "last_sync_on", "authorization_code", - "refresh_token" + "refresh_token", + "next_sync_token", + "sync", + "pull_from_google_contacts", + "column_break_12", + "push_to_google_contacts" ], "fields": [ { @@ -62,10 +67,38 @@ "fieldname": "authorize_google_contacts_access", "fieldtype": "Button", "label": "Authorize Google Contacts Access" + }, + { + "fieldname": "next_sync_token", + "fieldtype": "Password", + "hidden": 1, + "label": "Next Sync Token" + }, + { + "depends_on": "enable", + "fieldname": "sync", + "fieldtype": "Section Break", + "label": "Sync" + }, + { + "default": "0", + "fieldname": "pull_from_google_contacts", + "fieldtype": "Check", + "label": "Pull from Google Contacts" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "push_to_google_contacts", + "fieldtype": "Check", + "label": "Push to Google Contacts" } ], - "modified": "2019-08-23 13:50:52.789503", - "modified_by": "Administrator", + "modified": "2019-09-13 15:53:19.569924", + "modified_by": "himanshu@erpnext.com", "module": "Integrations", "name": "Google Contacts", "owner": "Administrator", diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 4955fc9d7f..738c097f63 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -5,14 +5,16 @@ from __future__ import unicode_literals import frappe import requests +import googleapiclient.discovery +import google.oauth2.credentials + from frappe.model.document import Document from frappe import _ +from googleapiclient.errors import HttpError from frappe.utils import get_request_site_address from frappe.integrations.doctype.google_settings.google_settings import get_auth_url SCOPES = "https://www.googleapis.com/auth/contacts" -REQUEST = "https://people.googleapis.com/v1/people/me/connections" -PARAMS = {"personFields": "names,emailAddresses,organizations,phoneNumbers"} class GoogleContacts(Document): @@ -60,7 +62,7 @@ def authorize_access(g_contact, reauthorize=None): if not google_contact.authorization_code or reauthorize: frappe.cache().hset("google_contacts", "google_contact", google_contact.name) - return google_callback(client_id=google_settings.client_id, redirect_uri=redirect_uri) + return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) else: try: data = { @@ -83,21 +85,42 @@ def authorize_access(g_contact, reauthorize=None): except Exception as e: frappe.throw(e) +def get_authentication_url(client_id=None, redirect_uri=None): + return { + "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) + } + @frappe.whitelist() -def google_callback(client_id=None, redirect_uri=None, code=None): +def google_callback(code=None): """ Authorization code is sent to callback as per the API configuration """ - if code is None: - return { - "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) - } - else: - google_contact = frappe.cache().hget("google_contacts", "google_contact") - frappe.db.set_value("Google Contacts", google_contact, "authorization_code", code) - frappe.db.commit() + google_contact = frappe.cache().hget("google_contacts", "google_contact") + frappe.db.set_value("Google Contacts", google_contact, "authorization_code", code) + frappe.db.commit() - authorize_access(google_contact) + authorize_access(google_contact) + +def get_google_contacts_object(g_contact): + """ + Returns an object of Google Calendar along with Google Calendar doc. + """ + google_settings = frappe.get_doc("Google Settings") + account = frappe.get_doc("Google Contacts", g_contact) + + credentials_dict = { + "token": account.get_access_token(), + "refresh_token": account.get_password(fieldname="refresh_token", raise_exception=False), + "token_uri": get_auth_url(), + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "scopes": "https://www.googleapis.com/auth/contacts" + } + + credentials = google.oauth2.credentials.Credentials(**credentials_dict) + google_contacts = googleapiclient.discovery.build("people", "v1", credentials=credentials) + + return google_contacts, account @frappe.whitelist() def sync(g_contact=None): @@ -108,58 +131,143 @@ def sync(g_contact=None): google_contacts = frappe.get_list("Google Contacts", filters=filters) - for google_contact in google_contacts: - doc = frappe.get_doc("Google Contacts", google_contact.name) - access_token = doc.get_access_token() + for g in google_contacts: + return sync_contacts_from_google_contacts(g.name) - headers = {"Authorization": "Bearer {}".format(access_token)} +def sync_contacts_from_google_contacts(g_contact): + """ + Syncs Contacts from Google Contacts. + https://developers.google.com/people/api/rest/v1/people.connections/list + """ + google_contacts, account = get_google_contacts_object(g_contact) + if not account.pull_from_google_contacts: + return + + results = [] + contacts_updated = 0 + + while True: try: - r = requests.get(REQUEST, headers=headers, params=PARAMS) - except Exception as e: - frappe.throw(e) + sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None + contacts = google_contacts.people().connections().list(resourceName='people/me',syncToken=sync_token, + personFields="names,emailAddresses,organizations,phoneNumbers").execute() + except HttpError as err: + frappe.throw(_("Google Contacts - Could not sync contacts from Google Contacts {0}, error code {1}.").format(account.name, err.resp.status)) - try: - r = r.json() - except Exception as e: - # if request doesn't return json show HTML ask permissions or to identify the error on google side - frappe.throw(e) + for contact in contacts.get("connections"): + results.append(contact) - connections = r.get("connections") - contacts_updated = 0 + if not contacts.get("nextPageToken"): + if contacts.get("nextSyncToken"): + frappe.db.set_value("Google Contacts", account.name, "next_sync_token", contacts.get("nextSyncToken")) + frappe.db.commit() + break - frappe.db.set_value("Google Contacts", doc.name, "last_sync_on", frappe.utils.now_datetime()) + frappe.db.set_value("Google Contacts", account.name, "last_sync_on", frappe.utils.now_datetime()) - if connections: - for idx, connection in enumerate(connections): - frappe.publish_realtime('import_google_contacts', dict(progress=idx+1, total=r.get("totalPeople")), user=frappe.session.user) + for idx, connection in enumerate(results): + frappe.publish_realtime('import_google_contacts', dict(progress=idx+1, total=len(results)), user=frappe.session.user) - for name in connection.get("names"): - if name.get("metadata").get("primary"): - contact = frappe.get_doc({ - "doctype": "Contact", - "salutation": name.get("honorificPrefix") or "", - "first_name": name.get("givenName") or "", - "middle_name": name.get("middleName") or "", - "last_name": name.get("familyName") or "", - "designation": get_indexed_value(connection.get("organizations"), 0, "title"), - "source": "Google Contacts", - "google_contacts_description": get_indexed_value(connection.get("organizations"), 0, "name") - }) + for name in connection.get("names"): + if name.get("metadata").get("primary"): + contacts_updated += 1 + contact = frappe.get_doc({ + "doctype": "Contact", + "first_name": name.get("givenName") or "", + "middle_name": name.get("middleName") or "", + "last_name": name.get("familyName") or "", + "designation": get_indexed_value(connection.get("organizations"), 0, "title"), + "pulled_from_google_contacts": 1, + "google_contacts": account.name, + "company_name": get_indexed_value(connection.get("organizations"), 0, "name") + }) - for email in connection.get("emailAddresses", []): - contact.add_email(email_id=email.get("value"), is_primary=1 if email.get("primary") else 0) + for email in connection.get("emailAddresses", []): + contact.add_email(email_id=email.get("value"), is_primary=1 if email.get("metadata").get("primary") else 0) - for phone in connection.get("phoneNumbers", []): - contact.add_phone(phone=phone.get("value"), is_primary=1 if phone.get("primary") else 0) + for phone in connection.get("phoneNumbers", []): + contact.add_phone(phone=phone.get("value"), is_primary=1 if phone.get("metadata").get("primary") else 0) - contact.insert(ignore_permissions=True) + contact.insert(ignore_permissions=True) - if g_contact: - return _("{0} Google Contacts synced.").format(contacts_updated) if contacts_updated > 0 else _("No new Google Contacts synced.") + return _("{0} Google Contacts synced.").format(contacts_updated) if contacts_updated > 0 \ + else _("No new Google Contacts synced.") - if g_contact: - return _("No Google Contacts present to sync.") # If no Google Contacts to sync +def insert_contacts_to_google_contacts(doc, method=None): + """ + Syncs Contacts from Google Contacts. + https://developers.google.com/people/api/rest/v1/people/createContact + """ + if not frappe.db.exists("Google Contacts", {"name": doc.google_contacts}) or doc.pulled_from_google_contacts \ + or not doc.sync_with_google_contacts: + return + + google_contacts, account = get_google_contacts_object(doc.google_contacts) + + if not account.push_to_google_contacts: + return + + names = { + "givenName": doc.first_name, + "middleName": doc.middle_name, + "familyName": doc.last_name + } + + phoneNumbers = [{"value": phone_no.phone} for phone_no in doc.phone_nos] + emailAddresses = [{"value": email_id.email_id} for email_id in doc.email_ids] + + try: + contact = google_contacts.people().createContact(parent='people/me', body={"names": [names],"phoneNumbers": phoneNumbers, + "emailAddresses": emailAddresses}).execute() + frappe.db.set_value("Contact", doc.name, "google_contacts_id", contact.get("resourceName")) + except HttpError as err: + frappe.msgprint(_("Google Calendar - Could not insert contact in Google Contacts {0}, error code {1}.").format(account.name, err.resp.status)) + +def update_contacts_to_google_contacts(doc, method=None): + """ + Syncs Contacts from Google Contacts. + https://developers.google.com/people/api/rest/v1/people/updateContact + """ + # Workaround to avoid triggering updation when Event is being inserted since + # creation and modified are same when inserting doc + if not frappe.db.exists("Google Contacts", {"name": doc.google_contacts}) or doc.is_new() \ + or not doc.sync_with_google_contacts: + return + + if doc.sync_with_google_contacts and not doc.google_contacts_id: + # If sync_with_google_contacts is checked later, then insert the contact rather than updating it. + insert_contacts_to_google_contacts(doc) + return + + google_contacts, account = get_google_contacts_object(doc.google_contacts) + + if not account.push_to_google_contacts: + return + + names = { + "givenName": doc.first_name, + "middleName": doc.middle_name, + "familyName": doc.last_name + } + + phoneNumbers = [{"value": phone_no.phone} for phone_no in doc.phone_nos] + emailAddresses = [{"value": email_id.email_id} for email_id in doc.email_ids] + + try: + contact = google_contacts.people().get(resourceName=doc.google_contacts_id, \ + personFields="names,emailAddresses,organizations,phoneNumbers").execute() + + contact["names"] = [names] + contact["phoneNumbers"] = phoneNumbers + contact["emailAddresses"] = emailAddresses + + google_contacts.people().updateContact(resourceName=doc.google_contacts_id,body={"names":[names], + "phoneNumbers":phoneNumbers,"emailAddresses":emailAddresses,"etag":contact.get("etag")}, + updatePersonFields="names,emailAddresses,organizations,phoneNumbers").execute() + frappe.msgprint(_("Contact Synced with Google Contacts.")) + except HttpError as err: + frappe.msgprint(_("Google Contacts - Could not update contact in Google Contacts {0}, error code {1}.").format(account.name, err.resp.status)) def get_indexed_value(d, index, key): if not d: diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 8a245b626e..a26c0f2012 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -259,7 +259,8 @@ class DatabaseQuery(object): # add tables from fields if self.fields: for f in self.fields: - if ( not ("tab" in f and "." in f) ) or ("locate(" in f) or ("strpos(" in f) or ("count(" in f): + if ( not ("tab" in f and "." in f) ) or ("locate(" in f) or ("strpos(" in f) or \ + ("count(" in f) or ("avg(" in f) or ("sum(" in f): continue table_name = f.split('.')[0] diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 456a12155f..83ccf2dc87 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -161,6 +161,9 @@ def delete_from_table(doctype, name, ignore_doctypes, doc): else: def get_table_fields(field_doctype): + if field_doctype == 'Custom Field': + return [] + return [r[0] for r in frappe.get_all(field_doctype, fields='options', filters={ diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index 967be8384e..7d79a9daf0 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -50,9 +50,6 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, elif isinstance(target_doc, string_types): target_doc = frappe.get_doc(json.loads(target_doc)) - if not ignore_permissions and not target_doc.has_permission("create"): - target_doc.raise_no_permission_to("create") - source_doc = frappe.get_doc(from_doctype, from_docname) if not ignore_permissions: @@ -113,6 +110,10 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, postprocess(source_doc, target_doc) target_doc.set_onload("load_after_mapping", True) + + if not ignore_permissions and not target_doc.has_permission("create"): + target_doc.raise_no_permission_to("create") + return target_doc def map_doc(source_doc, target_doc, table_map, source_parent=None): diff --git a/frappe/patches/v11_0/create_contact_for_user.py b/frappe/patches/v11_0/create_contact_for_user.py index fbb5a31fad..0e605a6a6b 100644 --- a/frappe/patches/v11_0/create_contact_for_user.py +++ b/frappe/patches/v11_0/create_contact_for_user.py @@ -5,6 +5,7 @@ import re def execute(): """ Create Contact for each User if not present """ + frappe.reload_doc('integrations', 'doctype', 'google_contacts') frappe.reload_doc('contacts', 'doctype', 'contact') frappe.reload_doc('core', 'doctype', 'dynamic_link') diff --git a/frappe/patches/v12_0/delete_duplicate_indexes.py b/frappe/patches/v12_0/delete_duplicate_indexes.py index 5ec60971cc..7f9e4369a2 100644 --- a/frappe/patches/v12_0/delete_duplicate_indexes.py +++ b/frappe/patches/v12_0/delete_duplicate_indexes.py @@ -19,6 +19,7 @@ def execute(): non_unique FROM information_schema.STATISTICS WHERE table_name=%s + AND column_name!='name' AND non_unique=0 ORDER BY index_name; """, table, as_dict=1) diff --git a/frappe/public/build.json b/frappe/public/build.json index 6cd7410e22..2c502c5f72 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -209,7 +209,8 @@ "public/js/frappe/chat.js", "public/js/frappe/social/social_factory.js", "public/js/frappe/utils/energy_point_utils.js", - "public/js/frappe/ui/chart.js" + "public/js/frappe/ui/chart.js", + "public/js/frappe/barcode_scanner/index.js" ], "css/module.min.css": [ "public/less/module.less" @@ -321,5 +322,8 @@ ], "js/modules.min.js": [ "public/js/frappe/views/modules_home.js" + ], + "js/barcode_scanner.min.js": [ + "public/js/frappe/barcode_scanner/quagga.js" ] } diff --git a/frappe/public/js/frappe/barcode_scanner/index.js b/frappe/public/js/frappe/barcode_scanner/index.js new file mode 100644 index 0000000000..c5e7a7600f --- /dev/null +++ b/frappe/public/js/frappe/barcode_scanner/index.js @@ -0,0 +1,23 @@ +frappe.provide('frappe.barcode'); + +frappe.barcode.scan_barcode = function() { + return new Promise((resolve, reject) => { + if ( + window.cordova && + window.cordova.plugins && + window.cordova.plugins.barcodeScanner + ) { + window.cordova.plugins.barcodeScanner.scan(result => { + if (!result.cancelled) { + resolve(result.text); + } + }, reject); + } else { + frappe.require('/assets/js/barcode_scanner.min.js', () => { + frappe.barcode.get_barcode().then(barcode => { + resolve(barcode); + }); + }); + } + }); +}; diff --git a/frappe/public/js/frappe/barcode_scanner/quagga.js b/frappe/public/js/frappe/barcode_scanner/quagga.js new file mode 100644 index 0000000000..fcab3b4dbe --- /dev/null +++ b/frappe/public/js/frappe/barcode_scanner/quagga.js @@ -0,0 +1,94 @@ +import Quagga from 'quagga/dist/quagga'; +frappe.provide('frappe.barcode'); + +Quagga.onProcessed(function(result) { + let drawingCtx = Quagga.canvas.ctx.overlay, + drawingCanvas = Quagga.canvas.dom.overlay; + + if (result) { + if (result.boxes) { + drawingCtx.clearRect( + 0, + 0, + parseInt(drawingCanvas.getAttribute('width')), + parseInt(drawingCanvas.getAttribute('height')) + ); + result.boxes + .filter(function(box) { + return box !== result.box; + }) + .forEach(function(box) { + Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { + color: 'green', + lineWidth: 2 + }); + }); + } + + if (result.box) { + Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { + color: '#00F', + lineWidth: 2 + }); + } + + if (result.codeResult && result.codeResult.code) { + Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { + color: 'red', + lineWidth: 3 + }); + } + } +}); + +frappe.barcode.get_barcode = function() { + return new Promise(resolve => { + let d = new frappe.ui.Dialog({ + title: __('Scan Barcode'), + fields: [ + { + fieldtype: 'HTML', + fieldname: 'scan_area' + } + ], + on_page_show() { + let $scan_area = d.get_field('scan_area').$wrapper; + $scan_area.addClass('barcode-scanner'); + + Quagga.init( + { + inputStream: { + name: 'Live', + type: 'LiveStream', + target: $scan_area.get(0) + }, + decoder: { + readers: ['code_128_reader'] + } + }, + function(err) { + if (err) { + // eslint-disable-next-line + console.log(err); + return; + } + // eslint-disable-next-line + console.log('Initialization finished. Ready to start'); + Quagga.start(); + } + ); + + Quagga.onDetected(function(result) { + let code = result.codeResult.code; + if (code) { + Quagga.stop(); + d.hide(); + resolve(code); + } + }); + } + }); + + d.show(); + }); +}; diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index e6c2328c73..db7ca76852 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -130,6 +130,23 @@ frappe.Application = Class.extend({ } } this.link_preview = new frappe.ui.LinkPreview(); + + if (!frappe.boot.developer_mode) { + setInterval(function() { + frappe.call({ + method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status', + callback: function(r) { + if (r.message[0] == __("Inactive")) { + frappe.msgprint({ + title: __("Scheduler Inactive"), + indicator: "red", + message: __("Background jobs are not running. Please contact Administrator") + }); + } + } + }); + }, 300000); // check every 5 minutes + } }, setup_frappe_vue() { diff --git a/frappe/public/js/frappe/form/controls/comment.js b/frappe/public/js/frappe/form/controls/comment.js index 44c564ad2c..eba82fd7b2 100644 --- a/frappe/public/js/frappe/form/controls/comment.js +++ b/frappe/public/js/frappe/form/controls/comment.js @@ -60,7 +60,7 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({ update_state() { const value = this.get_value(); - if (strip_html(value)) { + if (strip_html(value).trim() != "") { this.button.removeClass('btn-default').addClass('btn-primary'); } else { this.button.addClass('btn-default').removeClass('btn-primary'); diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index eb9d35398f..fbb047fbbd 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -30,7 +30,7 @@ frappe.ui.form.Timeline = class Timeline { render_input: true, only_input: true, on_submit: (val) => { - if(strip_html(val)) { + if(strip_html(val).trim() != "") { this.insert_comment(val, this.comment_area.button); } } @@ -562,33 +562,32 @@ frappe.ui.form.Timeline = class Timeline { build_version_comments(docinfo, out) { var me = this; docinfo.versions.forEach(function(version) { - if(!version.data) return; + if (!version.data) return; var data = JSON.parse(version.data); // comment - if(data.comment) { + if (data.comment) { out.push(me.get_version_comment(version, data.comment, data.comment_type)); return; } // value changed in parent - if(data.changed && data.changed.length) { - var parts = []; - data.changed.every(function(p) { - if(p[0]==='docstatus') { - if(p[2]==1) { + if (data.changed && data.changed.length) { + const parts = []; + data.changed.every(function (p) { + if (p[0] === 'docstatus') { + if (p[2] == 1) { out.push(me.get_version_comment(version, __('submitted this document'))); - } else if (p[2]==2) { + } else if (p[2] == 2) { out.push(me.get_version_comment(version, __('cancelled this document'))); } } else { - - var df = frappe.meta.get_docfield(me.frm.doctype, p[0], me.frm.docname); - - if(df && !df.hidden) { - var field_display_status = frappe.perm.get_field_display_status(df, null, + p = p.map(frappe.utils.escape_html); + const df = frappe.meta.get_docfield(me.frm.doctype, p[0], me.frm.docname); + if (df && !df.hidden) { + const field_display_status = frappe.perm.get_field_display_status(df, null, me.frm.perm); - if(field_display_status === 'Read' || field_display_status === 'Write') { + if (field_display_status === 'Read' || field_display_status === 'Write') { parts.push(__('{0} from {1} to {2}', [ __(df.label), (frappe.ellipsis(frappe.utils.html2text(p[1]), 40) || '""').bold(), @@ -599,9 +598,8 @@ frappe.ui.form.Timeline = class Timeline { } return parts.length < 3; }); - if(parts.length) { - parts = parts.map(frappe.utils.escape_html); - out.push(me.get_version_comment(version, __("changed value of {0}", [parts.join(', ').bold()]))); + if (parts.length) { + out.push(me.get_version_comment(version, __('changed value of {0}', [parts.join(', ')]))); } } diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 27db4d2200..53130afeb3 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -106,6 +106,15 @@ export default class BulkOperations { } } + apply_assignment_rule(docnames, done) { + if (docnames.length > 0) { + frappe.call('frappe.automation.doctype.assignment_rule.assignment_rule.bulk_apply', { + doctype: this.doctype, + docnames: docnames + }).then(() => done()); + } + } + submit_or_cancel(docnames, action='submit', done=null) { action = action.toLowerCase(); frappe diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 74b503e36b..5f9aac0afb 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1300,6 +1300,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }; }; + const bulk_assignment_rule = () => { + return { + label: __('Apply Assignment Rule'), + action: () => bulk_operations.apply_assignment_rule(this.get_checked_items(true), this.refresh), + standard: true + }; + }; + const bulk_printing = () => { return { label: __('Print'), @@ -1376,6 +1384,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { // bulk assignment actions_menu_items.push(bulk_assignment()); + actions_menu_items.push(bulk_assignment_rule()); + // bulk printing if (frappe.model.can_print(doctype)) { actions_menu_items.push(bulk_printing()); diff --git a/frappe/public/js/frappe/ui/group_by/group_by.html b/frappe/public/js/frappe/ui/group_by/group_by.html index dd8d3dd281..670019a22b 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.html +++ b/frappe/public/js/frappe/ui/group_by/group_by.html @@ -1,33 +1,48 @@
diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 613b5aeb5b..1497540816 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -17,6 +17,7 @@ frappe.ui.GroupBy = class { {name:'avg', label:'Average'} ]; this.groupby_edit_area = $(frappe.render_template("group_by", { + doctype: this.doctype, groupby_conditions: this.get_group_by_fields(), aggregate_function_conditions: sql_aggregate_function, })); @@ -102,7 +103,9 @@ frappe.ui.GroupBy = class { } apply_group_by() { - this.group_by = this.groupby_select.val(); + this.group_by_doctype = this.groupby_select.find(':selected').attr('data-doctype'); + this.group_by_field = this.groupby_select.val(); + this.group_by = '`tab' + this.group_by_doctype + '`.`' + this.group_by_field + '`'; this.aggregate_function = this.aggregate_function_select.val(); if (this.aggregate_function === 'count') { @@ -138,9 +141,9 @@ frappe.ui.GroupBy = class { if (this.aggregate_function && this.group_by) { let aggregate_column; if(this.aggregate_function === 'count') { - aggregate_column = 'count(1)'; + aggregate_column = 'count(`tab'+ this.doctype + '`.`name`)'; } else { - aggregate_column = `${this.aggregate_function}(${this.aggregate_on})`; + aggregate_column = `${this.aggregate_function}(\`tab${this.doctype}\`.\`${this.aggregate_on}\`)`; } this.report_view.group_by = this.group_by; @@ -153,7 +156,7 @@ frappe.ui.GroupBy = class { } this.report_view.fields = [ - [this.group_by, this.doctype] + [this.group_by_field, this.group_by_doctype] ]; // rebuild fields for group by @@ -223,7 +226,23 @@ frappe.ui.GroupBy = class { } get_group_by_fields() { - return this.report_view.meta.fields.filter((f)=> ["Select", "Link"].includes(f.fieldtype)); + let group_by_fields = {}; + let fields = this.report_view.meta.fields.filter(f => ["Select", "Link"].includes(f.fieldtype)); + group_by_fields[this.doctype] = fields; + + const standard_fields_filter = df => + !in_list(frappe.model.no_value_type, df.fieldtype) && !df.report_hide; + + const table_fields = frappe.meta.get_table_fields(this.doctype) + .filter(df => !df.hidden); + + table_fields.forEach(df => { + const cdt = df.options; + const child_table_fields = frappe.meta.get_docfields(cdt).filter(standard_fields_filter); + group_by_fields[cdt] = child_table_fields; + }); + + return group_by_fields; } }; diff --git a/frappe/public/js/frappe/web_form/webform_script.js b/frappe/public/js/frappe/web_form/webform_script.js index f4f140845f..ea913f163d 100644 --- a/frappe/public/js/frappe/web_form/webform_script.js +++ b/frappe/public/js/frappe/web_form/webform_script.js @@ -100,7 +100,9 @@ frappe.ready(function() { return df; } - if (df.fieldtype === "Link") df.only_select = true; + if (df.fieldtype === "Link") { + df.only_select = true; + } }); return form_data; diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index 07e4ab3ccd..809c271dfc 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -130,4 +130,19 @@ max-height: 200px; overflow: auto; } -} \ No newline at end of file +} + +.barcode-scanner { + position: relative; + + & > canvas, & > video { + max-width: 100%; + width: 100%; + } + + canvas.drawing, canvas.drawingBuffer { + position: absolute; + left: 0; + top: 0; + } +} diff --git a/frappe/templates/pages/integrations/stripe_checkout.html b/frappe/templates/pages/integrations/stripe_checkout.html index a4906afc30..ec3d9783c5 100644 --- a/frappe/templates/pages/integrations/stripe_checkout.html +++ b/frappe/templates/pages/integrations/stripe_checkout.html @@ -12,15 +12,15 @@ {%- block page_content -%} -