Merge pull request #7304 from rmehta/milestone-tracker
feat: Milestone Tracker. Track document lifecycle with milestones
This commit is contained in:
commit
01242afc8f
26 changed files with 657 additions and 90 deletions
|
|
@ -7,13 +7,14 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.form import assign_to
|
||||
import frappe.cache_manager
|
||||
|
||||
class AssignmentRule(Document):
|
||||
def on_update(self): # pylint: disable=no-self-use
|
||||
frappe.cache().delete_value('assignment_rule')
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.name)
|
||||
|
||||
def after_rename(self): # pylint: disable=no-self-use
|
||||
frappe.cache().delete_value('assignment_rule')
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.name)
|
||||
|
||||
def apply_unassign(self, doc, assignments):
|
||||
if (self.unassign_condition and
|
||||
|
|
@ -113,14 +114,14 @@ def apply(doc, method):
|
|||
if frappe.flags.in_patch or frappe.flags.in_install:
|
||||
return
|
||||
|
||||
assignment_rules = frappe.cache().get_value('assignment_rule', get_assignment_rules)
|
||||
assignment_rules = frappe.cache_manager.get_doctype_map('Assignment Rule', doc.doctype, dict(
|
||||
document_type = doc.doctype, disabled = 0), order_by = 'priority desc')
|
||||
|
||||
assignment_rule_docs = []
|
||||
|
||||
# build rules
|
||||
if doc.doctype in assignment_rules:
|
||||
# multiple auto assigns
|
||||
for d in frappe.db.get_all('Assignment Rule', dict(document_type=doc.doctype, disabled = 0), order_by = 'priority desc'):
|
||||
assignment_rule_docs.append(frappe.get_doc('Assignment Rule', d.name))
|
||||
# multiple auto assigns
|
||||
for d in assignment_rules:
|
||||
assignment_rule_docs.append(frappe.get_doc('Assignment Rule', d.name))
|
||||
|
||||
if not assignment_rule_docs:
|
||||
return
|
||||
|
|
|
|||
0
frappe/automation/doctype/milestone/__init__.py
Normal file
0
frappe/automation/doctype/milestone/__init__.py
Normal file
8
frappe/automation/doctype/milestone/milestone.js
Normal file
8
frappe/automation/doctype/milestone/milestone.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Milestone', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
230
frappe/automation/doctype/milestone/milestone.json
Normal file
230
frappe/automation/doctype/milestone/milestone.json
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2019-04-17 09:39:15.647817",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"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": "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": "Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"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": "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": "Document",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "track_field",
|
||||
"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": "Track Field",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "value",
|
||||
"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": "Value",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "milestone_tracker",
|
||||
"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": "Milestone Tracker",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Milestone Tracker",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-17 16:01:21.430344",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Milestone",
|
||||
"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": 1,
|
||||
"read_only": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"title_field": "reference_type",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
14
frappe/automation/doctype/milestone/milestone.py
Normal file
14
frappe/automation/doctype/milestone/milestone.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Milestone(Document):
|
||||
pass
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Milestone", ["reference_type", "reference_name"])
|
||||
10
frappe/automation/doctype/milestone/test_milestone.py
Normal file
10
frappe/automation/doctype/milestone/test_milestone.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
#import frappe
|
||||
import unittest
|
||||
|
||||
class TestMilestone(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/automation/doctype/milestone_tracker/__init__.py
Normal file
0
frappe/automation/doctype/milestone_tracker/__init__.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Milestone Tracker', {
|
||||
refresh: function(frm) {
|
||||
frm.trigger('update_options');
|
||||
},
|
||||
update_options: function(frm) {
|
||||
// update select options for `track_field`
|
||||
let doctype = frm.doc.document_type;
|
||||
let track_fields = [];
|
||||
|
||||
if (doctype) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
// get all date and datetime fields
|
||||
frappe.get_meta(doctype).fields.map(df => {
|
||||
if (['Link', 'Select'].includes(df.fieldtype)) {
|
||||
track_fields.push({label: df.label, value: df.fieldname});
|
||||
}
|
||||
});
|
||||
frm.set_df_property('track_field', 'options', track_fields);
|
||||
});
|
||||
} else {
|
||||
// update select options
|
||||
frm.set_df_property('track_field', 'options', []);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "format:{document_type}-{track_field}",
|
||||
"beta": 0,
|
||||
"creation": "2019-04-17 09:36:41.774774",
|
||||
"custom": 0,
|
||||
"description": "Track milestones for any document",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"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": "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": "Document Type to Track",
|
||||
"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": 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": "track_field",
|
||||
"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": "Field to Track",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 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
|
||||
}
|
||||
],
|
||||
"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-17 14:48:29.510679",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Milestone Tracker",
|
||||
"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": 1,
|
||||
"read_only": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
import frappe.cache_manager
|
||||
|
||||
class MilestoneTracker(Document):
|
||||
def on_update(self):
|
||||
frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.name)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.name)
|
||||
|
||||
def apply(self, doc):
|
||||
before_save = doc.get_doc_before_save()
|
||||
from_value = before_save and before_save.get(self.track_field) or None
|
||||
if from_value != doc.get(self.track_field):
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Milestone',
|
||||
reference_type = doc.doctype,
|
||||
reference_name = doc.name,
|
||||
track_field = self.track_field,
|
||||
from_value = from_value,
|
||||
value = doc.get(self.track_field),
|
||||
milestone_tracker = self.name,
|
||||
)).insert()
|
||||
|
||||
def evaluate_milestone(doc, event):
|
||||
for d in frappe.cache_manager.get_doctype_map('Milestone Tracker', doc.doctype,
|
||||
dict(document_type = doc.doctype, disabled=0)):
|
||||
frappe.get_doc('Milestone Tracker', d.name).apply(doc)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestMilestoneTracker(unittest.TestCase):
|
||||
def test_milestone(self):
|
||||
frappe.db.sql('delete from `tabMilestone Tracker`')
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Milestone Tracker',
|
||||
document_type = 'ToDo',
|
||||
track_field = 'status'
|
||||
)).insert()
|
||||
|
||||
todo = frappe.get_doc(dict(
|
||||
doctype = 'ToDo',
|
||||
description = 'test milestone'
|
||||
)).insert()
|
||||
|
||||
milestones = frappe.get_all('Milestone',
|
||||
fields = ['track_field', 'value', 'milestone_tracker'],
|
||||
filters = dict(reference_type = todo.doctype, reference_name=todo.name))
|
||||
|
||||
self.assertEqual(len(milestones), 1)
|
||||
self.assertEqual(milestones[0].track_field, 'status')
|
||||
self.assertEqual(milestones[0].value, 'Open')
|
||||
|
||||
todo.status = 'Closed'
|
||||
todo.save()
|
||||
|
||||
milestones = frappe.get_all('Milestone',
|
||||
fields = ['track_field', 'value', 'milestone_tracker'],
|
||||
filters = dict(reference_type = todo.doctype, reference_name=todo.name),
|
||||
order_by = 'modified desc')
|
||||
|
||||
self.assertEqual(len(milestones), 2)
|
||||
self.assertEqual(milestones[0].track_field, 'status')
|
||||
self.assertEqual(milestones[0].value, 'Closed')
|
||||
|
||||
|
|
@ -3,31 +3,40 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import frappe, json
|
||||
import frappe.defaults
|
||||
from frappe.desk.notifications import (delete_notification_count_for,
|
||||
clear_notifications)
|
||||
|
||||
common_default_keys = ["__default", "__global"]
|
||||
|
||||
def clear_user_cache(user=None):
|
||||
cache = frappe.cache()
|
||||
global_cache_keys = ("app_hooks", "installed_apps",
|
||||
"app_modules", "module_app", "notification_config", 'system_settings',
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule')
|
||||
|
||||
groups = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
||||
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
||||
"defaults", "user_permissions", "home_page", "linked_with",
|
||||
"desktop_icons", 'portal_menu_items')
|
||||
|
||||
doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified",
|
||||
"linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map')
|
||||
|
||||
|
||||
def clear_user_cache(user=None):
|
||||
cache = frappe.cache()
|
||||
|
||||
# this will automatically reload the global cache
|
||||
# so it is important to clear this first
|
||||
clear_notifications(user)
|
||||
|
||||
if user:
|
||||
for name in groups:
|
||||
for name in user_cache_keys:
|
||||
cache.hdel(name, user)
|
||||
cache.delete_keys("user:" + user)
|
||||
clear_defaults_cache(user)
|
||||
else:
|
||||
for name in groups:
|
||||
for name in user_cache_keys:
|
||||
cache.delete_key(name)
|
||||
clear_defaults_cache()
|
||||
clear_global_cache()
|
||||
|
|
@ -37,10 +46,7 @@ def clear_global_cache():
|
|||
|
||||
clear_doctype_cache()
|
||||
clear_website_cache()
|
||||
frappe.cache().delete_value(["app_hooks", "installed_apps",
|
||||
"app_modules", "module_app", "notification_config", 'system_settings',
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule'])
|
||||
frappe.cache().delete_value(global_cache_keys)
|
||||
frappe.setup_module_map()
|
||||
|
||||
def clear_defaults_cache(user=None):
|
||||
|
|
@ -63,11 +69,8 @@ def clear_doctype_cache(doctype=None):
|
|||
for key in ('is_table', 'doctype_modules'):
|
||||
cache.delete_value(key)
|
||||
|
||||
groups = ["meta", "form_meta", "table_columns", "last_modified",
|
||||
"linked_doctypes", 'notifications', 'workflow']
|
||||
|
||||
def clear_single(dt):
|
||||
for name in groups:
|
||||
for name in doctype_cache_keys:
|
||||
cache.hdel(name, dt)
|
||||
|
||||
if doctype:
|
||||
|
|
@ -84,9 +87,31 @@ def clear_doctype_cache(doctype=None):
|
|||
|
||||
else:
|
||||
# clear all
|
||||
for name in groups:
|
||||
for name in doctype_cache_keys:
|
||||
cache.delete_value(name)
|
||||
|
||||
# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
|
||||
clear_document_cache()
|
||||
|
||||
def get_doctype_map(doctype, name, filters, order_by=None):
|
||||
cache = frappe.cache()
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
doctype_map = cache.hget(cache_key, name)
|
||||
|
||||
if doctype_map:
|
||||
# cached, return
|
||||
items = json.loads(doctype_map)
|
||||
else:
|
||||
# non cached, build cache
|
||||
try:
|
||||
items = frappe.get_all(doctype, filters=filters, order_by = order_by)
|
||||
cache.hset(cache_key, doctype, json.dumps(items))
|
||||
except frappe.db.TableMissingError:
|
||||
# executed from inside patch, ignore
|
||||
items = []
|
||||
|
||||
return items
|
||||
|
||||
def clear_doctype_map(doctype, name):
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
frappe.cache().hdel(cache_key, name)
|
||||
|
|
@ -40,6 +40,9 @@ def get_diff(old, new, for_child=False):
|
|||
],
|
||||
|
||||
}'''
|
||||
if not new:
|
||||
return None
|
||||
|
||||
out = frappe._dict(changed = [], added = [], removed = [], row_changed = [])
|
||||
for df in new.meta.fields:
|
||||
if df.fieldtype in no_value_fields and df.fieldtype not in table_fields:
|
||||
|
|
|
|||
|
|
@ -833,7 +833,7 @@ class Database(object):
|
|||
"""Returns list of column names from given doctype."""
|
||||
columns = self.get_db_table_columns('tab' + doctype)
|
||||
if not columns:
|
||||
raise self.ProgrammingError
|
||||
raise self.TableMissingError
|
||||
return columns
|
||||
|
||||
def has_column(self, doctype, column):
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from frappe.database.mariadb.schema import MariaDBTable
|
|||
|
||||
class MariaDBDatabase(Database):
|
||||
ProgrammingError = pymysql.err.ProgrammingError
|
||||
TableMissingError = pymysql.err.ProgrammingError
|
||||
OperationalError = pymysql.err.OperationalError
|
||||
InternalError = pymysql.err.InternalError
|
||||
SQLError = pymysql.err.ProgrammingError
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ psycopg2.extensions.register_type(DEC2FLOAT)
|
|||
|
||||
class PostgresDatabase(Database):
|
||||
ProgrammingError = psycopg2.ProgrammingError
|
||||
TableMissingError = psycopg2.ProgrammingError
|
||||
OperationalError = psycopg2.OperationalError
|
||||
InternalError = psycopg2.InternalError
|
||||
SQLError = psycopg2.ProgrammingError
|
||||
|
|
|
|||
|
|
@ -76,12 +76,17 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
frm.trigger('render_filters_table');
|
||||
} else {
|
||||
if (frm.doc.chart_type==='Custom') {
|
||||
frappe.xcall('frappe.desk.doctype.dashboard_chart_source.dashboard_chart_source.get_config', {name: frm.doc.source})
|
||||
.then(config => {
|
||||
frappe.dom.eval(config);
|
||||
frm.filters = frappe.dashboards.chart_sources[frm.doc.source].filters;
|
||||
frm.trigger('render_filters_table');
|
||||
});
|
||||
if (frm.doc.source) {
|
||||
frappe.xcall('frappe.desk.doctype.dashboard_chart_source.dashboard_chart_source.get_config', {name: frm.doc.source})
|
||||
.then(config => {
|
||||
frappe.dom.eval(config);
|
||||
frm.filters = frappe.dashboards.chart_sources[frm.doc.source].filters;
|
||||
frm.trigger('render_filters_table');
|
||||
});
|
||||
} else {
|
||||
frm.filters = [];
|
||||
frm.trigger('render_filters_table');
|
||||
}
|
||||
} else {
|
||||
// standard filters
|
||||
if (frm.doc.document_type) {
|
||||
|
|
|
|||
|
|
@ -103,10 +103,14 @@ def get_docinfo(doc=None, doctype=None, name=None):
|
|||
"rating": get_feedback_rating(doc.doctype, doc.name),
|
||||
"views": get_view_logs(doc.doctype, doc.name),
|
||||
"energy_point_logs": get_point_logs(doc.doctype, doc.name),
|
||||
"is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user),
|
||||
"document_follow_enabled": frappe.db.get_value("User", frappe.session.user, "document_follow_notify")
|
||||
"milestones": get_milestones(doc.doctype, doc.name),
|
||||
"is_document_followed": is_document_followed(doc.doctype, doc.name, frappe.session.user)
|
||||
}
|
||||
|
||||
def get_milestones(doctype, name):
|
||||
return frappe.db.get_all('Milestone', fields = ['creation', 'owner', 'track_field', 'value'],
|
||||
filters=dict(reference_type=doctype, reference_name=name))
|
||||
|
||||
def get_attachments(dt, dn):
|
||||
return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"],
|
||||
filters = {"attached_to_name": dn, "attached_to_doctype": dt})
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ doc_events = {
|
|||
"frappe.core.doctype.activity_log.feed.update_feed",
|
||||
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions",
|
||||
"frappe.automation.doctype.assignment_rule.assignment_rule.apply",
|
||||
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points"
|
||||
"frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points",
|
||||
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone"
|
||||
],
|
||||
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
|
||||
"on_cancel": [
|
||||
|
|
|
|||
|
|
@ -117,6 +117,12 @@ class Document(BaseDocument):
|
|||
# incorrect arguments. let's not proceed.
|
||||
raise ValueError('Illegal arguments')
|
||||
|
||||
@staticmethod
|
||||
def whitelist(f):
|
||||
"""Decorator: Whitelist method to be called remotely via REST API."""
|
||||
f.whitelisted = True
|
||||
return f
|
||||
|
||||
def reload(self):
|
||||
"""Reload document from database"""
|
||||
self.load_from_db()
|
||||
|
|
@ -370,13 +376,7 @@ class Document(BaseDocument):
|
|||
(self.name, self.doctype, fieldname))
|
||||
|
||||
def get_doc_before_save(self):
|
||||
if not getattr(self, '_doc_before_save', None):
|
||||
try:
|
||||
self._doc_before_save = frappe.get_doc(self.doctype, self.name)
|
||||
except frappe.DoesNotExistError:
|
||||
self._doc_before_save = None
|
||||
frappe.clear_last_message()
|
||||
return self._doc_before_save
|
||||
return getattr(self, '_doc_before_save', None)
|
||||
|
||||
def set_new_name(self, force=False):
|
||||
"""Calls `frappe.naming.se_new_name` for parent and child docs."""
|
||||
|
|
@ -834,11 +834,6 @@ class Document(BaseDocument):
|
|||
elif alert.event=='Method' and method == alert.method:
|
||||
_evaluate_alert(alert)
|
||||
|
||||
@staticmethod
|
||||
def whitelist(f):
|
||||
f.whitelisted = True
|
||||
return f
|
||||
|
||||
@whitelist.__func__
|
||||
def _submit(self):
|
||||
"""Submit the document. Sets `docstatus` = 1, then saves."""
|
||||
|
|
@ -899,11 +894,12 @@ class Document(BaseDocument):
|
|||
def load_doc_before_save(self):
|
||||
'''Save load document from db before saving'''
|
||||
self._doc_before_save = None
|
||||
if not (self.is_new()
|
||||
and (getattr(self.meta, 'track_changes', False)
|
||||
or self.meta.get_set_only_once_fields()
|
||||
or self.meta.get_workflow())):
|
||||
self.get_doc_before_save()
|
||||
if not self.is_new():
|
||||
try:
|
||||
self._doc_before_save = frappe.get_doc(self.doctype, self.name)
|
||||
except frappe.DoesNotExistError:
|
||||
self._doc_before_save = None
|
||||
frappe.clear_last_message()
|
||||
|
||||
def run_post_save_methods(self):
|
||||
"""Run standard methods after `INSERT` or `UPDATE`. Standard Methods are:
|
||||
|
|
@ -1018,12 +1014,6 @@ class Document(BaseDocument):
|
|||
if not frappe.flags.in_migrate:
|
||||
follow_document(self.doctype, self.name, frappe.session.user)
|
||||
|
||||
@staticmethod
|
||||
def whitelist(f):
|
||||
"""Decorator: Whitelist method to be called remotely via REST API."""
|
||||
f.whitelisted = True
|
||||
return f
|
||||
|
||||
@staticmethod
|
||||
def hook(f):
|
||||
"""Decorator: Make method `hookable` (i.e. extensible by another app).
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ frappe.ui.form.DocumentFollow = class DocumentFollow {
|
|||
|
||||
render_sidebar() {
|
||||
const docinfo = this.frm.get_docinfo();
|
||||
const document_follow_enabled = docinfo && docinfo.document_follow_enabled;
|
||||
const document_follow_enabled = frappe.boot.user.document_follow_notify;
|
||||
const document_can_be_followed = frappe.get_meta(this.frm.doctype).track_changes;
|
||||
if (frappe.session.user === 'Administrator'
|
||||
|| !document_follow_enabled
|
||||
|
|
|
|||
|
|
@ -163,6 +163,9 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
// append energy point logs
|
||||
timeline = timeline.concat(this.get_energy_point_logs());
|
||||
|
||||
// append milestones
|
||||
timeline = timeline.concat(this.get_milestones());
|
||||
|
||||
// sort
|
||||
timeline
|
||||
.filter(a => a.content)
|
||||
|
|
@ -429,7 +432,6 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
if(c.communication_type == "Feedback"){
|
||||
c.icon = "octicon octicon-comment-discussion"
|
||||
c.rating_icons = frappe.render_template("rating_icons", {rating: c.rating, show_label: true})
|
||||
c.color = "#f39c12"
|
||||
} else {
|
||||
c.icon = {
|
||||
"Email": "octicon octicon-mail",
|
||||
|
|
@ -439,12 +441,12 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
"Event": "fa fa-calendar",
|
||||
"Meeting": "octicon octicon-briefcase",
|
||||
"ToDo": "fa fa-check",
|
||||
"Created": "octicon octicon-plus",
|
||||
"Submitted": "octicon octicon-lock",
|
||||
"Cancelled": "octicon octicon-x",
|
||||
"Assigned": "octicon octicon-person",
|
||||
"Assignment Completed": "octicon octicon-check",
|
||||
"Comment": "octicon octicon-comment-discussion",
|
||||
"Milestone": "octicon octicon-milestone",
|
||||
"Workflow": "octicon octicon-git-branch",
|
||||
"Label": "octicon octicon-tag",
|
||||
"Attachment": "octicon octicon-cloud-upload",
|
||||
|
|
@ -457,25 +459,6 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
"Reply": "octicon octicon-mail-reply"
|
||||
}[c.comment_type || c.communication_medium]
|
||||
|
||||
c.color = {
|
||||
"Email": "#3498db",
|
||||
"Chat": "#3498db",
|
||||
"Phone": "#3498db",
|
||||
"SMS": "#3498db",
|
||||
"Created": "#1abc9c",
|
||||
"Submitted": "#1abc9c",
|
||||
"Cancelled": "#c0392b",
|
||||
"Assigned": "#f39c12",
|
||||
"Assignment Completed": "#16a085",
|
||||
"Comment": "#f39c12",
|
||||
"Workflow": "#2c3e50",
|
||||
"Label": "#2c3e50",
|
||||
"Attachment": "#7f8c8d",
|
||||
"Attachment Removed": "#eee",
|
||||
"Relinked": "#16a085",
|
||||
"Reply": "#8d99a6"
|
||||
}[c.comment_type || c.communication_medium];
|
||||
|
||||
c.icon_fg = {
|
||||
"Attachment Removed": "#333",
|
||||
}[c.comment_type || c.communication_medium]
|
||||
|
|
@ -528,6 +511,21 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
return energy_point_logs;
|
||||
}
|
||||
|
||||
get_milestones() {
|
||||
let milestones = this.frm.get_docinfo().milestones;
|
||||
milestones.map(log => {
|
||||
log.color = 'dark';
|
||||
log.sender = log.owner;
|
||||
log.comment_type = 'Milestone';
|
||||
log.content = __('{0} changed {1} to {2}', [
|
||||
frappe.user.full_name(log.owner).bold(),
|
||||
frappe.meta.get_label(this.frm.doctype, log.track_field),
|
||||
log.value.bold()]);
|
||||
return log;
|
||||
});
|
||||
return milestones;
|
||||
}
|
||||
|
||||
cast_comment_as_communication(c) {
|
||||
c.sender = c.comment_email;
|
||||
c.sender_full_name = c.comment_by;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<div class="media timeline-item {% if (data.user_content) { %} user-content {% } else { %} notification-content {% } %}" data-doctype="{{ data.doctype }}" data-name="{%= data.name %}" data-communication-type = "{{ data.communication_type }}">
|
||||
<div class="media timeline-item {% if (data.user_content) { %} user-content {% } else { %} notification-content {% } %} {{ data.color || "" }}"
|
||||
data-doctype="{{ data.doctype }}" data-name="{{ data.name }}" data-communication-type = "{{ data.communication_type }}">
|
||||
{% if (data.user_content) { %}
|
||||
<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
|
||||
{% if(data.user_info.image) { %}
|
||||
|
|
@ -148,7 +149,7 @@
|
|||
</div>
|
||||
|
||||
{% } else if(in_list(["Assignment Completed", "Assigned", "Shared",
|
||||
"Unshared"], data.comment_type)) { %}
|
||||
"Unshared", "Milestone"], data.comment_type)) { %}
|
||||
<div class="small">
|
||||
<i class="{%= data.icon %} fa-fw"></i>
|
||||
{% if (data.timeline_doctype===data.frm.doc.doctype
|
||||
|
|
@ -162,7 +163,7 @@
|
|||
{% if(data.link_doctype && data.link_name) { %}
|
||||
<a href="#Form/{%= data.link_doctype %}/{%= data.link_name %}">
|
||||
{% } %}
|
||||
{%= __(data.content) %}
|
||||
{{ __(data.content) }}
|
||||
{% if(data.link_doctype && data.link_name) { %}
|
||||
</a>
|
||||
{% } %}
|
||||
|
|
|
|||
|
|
@ -574,6 +574,10 @@ h6.uppercase, .h6.uppercase {
|
|||
.timeline-indicator();
|
||||
}
|
||||
|
||||
.timeline-item.notification-content.dark::before {
|
||||
background-color: @text-color;
|
||||
}
|
||||
|
||||
.timeline-item .reply-link {
|
||||
margin-left: 15px;
|
||||
font-size: 12px;
|
||||
|
|
|
|||
|
|
@ -4,11 +4,18 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.cache_manager
|
||||
from frappe.model.document import Document
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
from frappe.social.doctype.energy_point_log.energy_point_log import create_energy_points_log
|
||||
|
||||
class EnergyPointRule(Document):
|
||||
def on_update(self):
|
||||
frappe.cache_manager.clear_doctype_map('Energy Point Rule', self.name)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache_manager.clear_doctype_map('Energy Point Rule', self.name)
|
||||
|
||||
def apply(self, doc):
|
||||
if frappe.safe_eval(self.condition, None, {'doc': doc.as_dict()}):
|
||||
multiplier = 1
|
||||
|
|
@ -41,14 +48,9 @@ class EnergyPointRule(Document):
|
|||
def process_energy_points(doc, state):
|
||||
if frappe.flags.in_patch or frappe.flags.in_install or not is_energy_point_enabled():
|
||||
return
|
||||
# TODO: cache properly
|
||||
# energy_point_doctypes = frappe.cache().get_value('energy_point_doctypes', get_energy_point_doctypes)
|
||||
# if doc.doctype in energy_point_doctypes:
|
||||
rules = frappe.get_all('Energy Point Rule', filters={
|
||||
'reference_doctype': doc.doctype,
|
||||
'enabled': 1
|
||||
})
|
||||
for d in rules:
|
||||
|
||||
for d in frappe.cache_manager.get_doctype_map('Energy Point Rule', doc.doctype,
|
||||
dict(reference_doctype = doc.doctype, enabled=1)):
|
||||
frappe.get_doc('Energy Point Rule', d.name).apply(doc)
|
||||
|
||||
def get_energy_point_doctypes():
|
||||
|
|
|
|||
|
|
@ -198,7 +198,8 @@ class UserPermissions:
|
|||
def load_user(self):
|
||||
d = frappe.db.sql("""select email, first_name, last_name, creation,
|
||||
email_signature, user_type, language, background_style, background_image,
|
||||
mute_sounds, send_me_a_copy from tabUser where name = %s""", (self.name,), as_dict=1)[0]
|
||||
mute_sounds, send_me_a_copy, document_follow_notify
|
||||
from tabUser where name = %s""", (self.name,), as_dict=1)[0]
|
||||
|
||||
if not self.can_read:
|
||||
self.build_permissions()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue