feat: Delayed Tasks Summary (#25024)
* feat: delayed deliverables summary * fix: sider * fix: renamed to delayed tasks * fix: renamed test * fix: test * fix: sider * fix: dates, validations and chart * fix: space and column width * feat: Sort tasks by descending order of delay Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
This commit is contained in:
parent
ac8a467b0a
commit
119b27b97f
@ -11,15 +11,16 @@
|
||||
"project",
|
||||
"issue",
|
||||
"type",
|
||||
"color",
|
||||
"is_group",
|
||||
"is_template",
|
||||
"column_break0",
|
||||
"status",
|
||||
"priority",
|
||||
"task_weight",
|
||||
"completed_by",
|
||||
"color",
|
||||
"parent_task",
|
||||
"completed_by",
|
||||
"completed_on",
|
||||
"sb_timeline",
|
||||
"exp_start_date",
|
||||
"expected_time",
|
||||
@ -358,6 +359,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.status == \"Completed\"",
|
||||
"fieldname": "completed_by",
|
||||
"fieldtype": "Link",
|
||||
"label": "Completed By",
|
||||
@ -381,6 +383,13 @@
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Int",
|
||||
"label": "Duration (Days)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.status == \"Completed\"",
|
||||
"fieldname": "completed_on",
|
||||
"fieldtype": "Date",
|
||||
"label": "Completed On",
|
||||
"mandatory_depends_on": "eval: doc.status == \"Completed\""
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-check",
|
||||
@ -388,7 +397,7 @@
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-12-28 11:32:58.714991",
|
||||
"modified": "2021-04-16 12:46:51.556741",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Task",
|
||||
|
@ -36,6 +36,7 @@ class Task(NestedSet):
|
||||
self.validate_status()
|
||||
self.update_depends_on()
|
||||
self.validate_dependencies_for_template_task()
|
||||
self.validate_completed_on()
|
||||
|
||||
def validate_dates(self):
|
||||
if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
|
||||
@ -100,6 +101,10 @@ class Task(NestedSet):
|
||||
dependent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(task.task)
|
||||
frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format))
|
||||
|
||||
def validate_completed_on(self):
|
||||
if self.completed_on and getdate(self.completed_on) > getdate():
|
||||
frappe.throw(_("Completed On cannot be greater than Today"))
|
||||
|
||||
def update_depends_on(self):
|
||||
depends_on_tasks = self.depends_on_tasks or ""
|
||||
for d in self.depends_on:
|
||||
|
@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Delayed Tasks Summary"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "priority",
|
||||
"label": __("Priority"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["", "Low", "Medium", "High", "Urgent"]
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"label": __("Status"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["", "Open", "Working","Pending Review","Overdue","Completed"]
|
||||
},
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
if (column.id == "delay") {
|
||||
if (data["delay"] > 0) {
|
||||
value = `<p style="color: red; font-weight: bold">${value}</p>`;
|
||||
} else {
|
||||
value = `<p style="color: green; font-weight: bold">${value}</p>`;
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-03-25 15:03:19.857418",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-04-15 15:49:35.432486",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Delayed Tasks Summary",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Task",
|
||||
"report_name": "Delayed Tasks Summary",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Projects User"
|
||||
},
|
||||
{
|
||||
"role": "Projects Manager"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import date_diff, nowdate
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
data = get_data(filters)
|
||||
columns = get_columns()
|
||||
charts = get_chart_data(data)
|
||||
return columns, data, None, charts
|
||||
|
||||
def get_data(filters):
|
||||
conditions = get_conditions(filters)
|
||||
tasks = frappe.get_all("Task",
|
||||
filters = conditions,
|
||||
fields = ["name", "subject", "exp_start_date", "exp_end_date",
|
||||
"status", "priority", "completed_on", "progress"],
|
||||
order_by="creation"
|
||||
)
|
||||
for task in tasks:
|
||||
if task.exp_end_date:
|
||||
if task.completed_on:
|
||||
task.delay = date_diff(task.completed_on, task.exp_end_date)
|
||||
elif task.status == "Completed":
|
||||
# task is completed but completed on is not set (for older tasks)
|
||||
task.delay = 0
|
||||
else:
|
||||
# task not completed
|
||||
task.delay = date_diff(nowdate(), task.exp_end_date)
|
||||
else:
|
||||
# task has no end date, hence no delay
|
||||
task.delay = 0
|
||||
|
||||
# Sort by descending order of delay
|
||||
tasks.sort(key=lambda x: x["delay"], reverse=True)
|
||||
return tasks
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = frappe._dict()
|
||||
keys = ["priority", "status"]
|
||||
for key in keys:
|
||||
if filters.get(key):
|
||||
conditions[key] = filters.get(key)
|
||||
if filters.get("from_date"):
|
||||
conditions.exp_end_date = [">=", filters.get("from_date")]
|
||||
if filters.get("to_date"):
|
||||
conditions.exp_start_date = ["<=", filters.get("to_date")]
|
||||
return conditions
|
||||
|
||||
def get_chart_data(data):
|
||||
delay, on_track = 0, 0
|
||||
for entry in data:
|
||||
if entry.get("delay") > 0:
|
||||
delay = delay + 1
|
||||
else:
|
||||
on_track = on_track + 1
|
||||
charts = {
|
||||
"data": {
|
||||
"labels": ["On Track", "Delayed"],
|
||||
"datasets": [
|
||||
{
|
||||
"name": "Delayed",
|
||||
"values": [on_track, delay]
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "percentage",
|
||||
"colors": ["#84D5BA", "#CB4B5F"]
|
||||
}
|
||||
return charts
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Task",
|
||||
"options": "Task",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subject",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Data",
|
||||
"label": "Status",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Data",
|
||||
"label": "Priority",
|
||||
"width": 80
|
||||
},
|
||||
{
|
||||
"fieldname": "progress",
|
||||
"fieldtype": "Data",
|
||||
"label": "Progress (%)",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "exp_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected Start Date",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "exp_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Expected End Date",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "completed_on",
|
||||
"fieldtype": "Date",
|
||||
"label": "Actual End Date",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"fieldname": "delay",
|
||||
"fieldtype": "Data",
|
||||
"label": "Delay (In Days)",
|
||||
"width": 120
|
||||
}
|
||||
]
|
||||
return columns
|
@ -0,0 +1,54 @@
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.utils import nowdate, add_days, add_months
|
||||
from erpnext.projects.doctype.task.test_task import create_task
|
||||
from erpnext.projects.report.delayed_tasks_summary.delayed_tasks_summary import execute
|
||||
|
||||
class TestDelayedTasksSummary(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUp(self):
|
||||
task1 = create_task("_Test Task 98", add_days(nowdate(), -10), nowdate())
|
||||
create_task("_Test Task 99", add_days(nowdate(), -10), add_days(nowdate(), -1))
|
||||
|
||||
task1.status = "Completed"
|
||||
task1.completed_on = add_days(nowdate(), -1)
|
||||
task1.save()
|
||||
|
||||
def test_delayed_tasks_summary(self):
|
||||
filters = frappe._dict({
|
||||
"from_date": add_months(nowdate(), -1),
|
||||
"to_date": nowdate(),
|
||||
"priority": "Low",
|
||||
"status": "Open"
|
||||
})
|
||||
expected_data = [
|
||||
{
|
||||
"subject": "_Test Task 99",
|
||||
"status": "Open",
|
||||
"priority": "Low",
|
||||
"delay": 1
|
||||
},
|
||||
{
|
||||
"subject": "_Test Task 98",
|
||||
"status": "Completed",
|
||||
"priority": "Low",
|
||||
"delay": -1
|
||||
}
|
||||
]
|
||||
report = execute(filters)
|
||||
data = list(filter(lambda x: x.subject == "_Test Task 99", report[1]))[0]
|
||||
|
||||
for key in ["subject", "status", "priority", "delay"]:
|
||||
self.assertEqual(expected_data[0].get(key), data.get(key))
|
||||
|
||||
filters.status = "Completed"
|
||||
report = execute(filters)
|
||||
data = list(filter(lambda x: x.subject == "_Test Task 98", report[1]))[0]
|
||||
|
||||
for key in ["subject", "status", "priority", "delay"]:
|
||||
self.assertEqual(expected_data[1].get(key), data.get(key))
|
||||
|
||||
def tearDown(self):
|
||||
for task in ["_Test Task 98", "_Test Task 99"]:
|
||||
frappe.get_doc("Task", {"subject": task}).delete()
|
@ -15,6 +15,7 @@
|
||||
"hide_custom": 0,
|
||||
"icon": "project",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Projects",
|
||||
"links": [
|
||||
@ -148,9 +149,19 @@
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Task",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Delayed Tasks Summary",
|
||||
"link_to": "Delayed Tasks Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2020-12-01 13:38:37.856224",
|
||||
"modified": "2021-03-26 16:32:00.628561",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Projects",
|
||||
|
Loading…
Reference in New Issue
Block a user