Merge pull request #24092 from pateljannat/project-template-and-tasks

feat: Project template with dependent tasks
This commit is contained in:
Deepesh Garg 2021-01-12 11:28:35 +05:30 committed by GitHub
commit 7fd2b35a7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 548 additions and 475 deletions

View File

@ -742,5 +742,6 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll
erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
erpnext.patches.v13_0.add_po_to_global_search erpnext.patches.v13_0.add_po_to_global_search
erpnext.patches.v13_0.update_returned_qty_in_pr_dn erpnext.patches.v13_0.update_returned_qty_in_pr_dn
erpnext.patches.v13_0.update_project_template_tasks
erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.set_company_in_leave_ledger_entry
erpnext.patches.v13_0.convert_qi_parameter_to_link_field erpnext.patches.v13_0.convert_qi_parameter_to_link_field

View File

@ -0,0 +1,44 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("projects", "doctype", "project_template")
frappe.reload_doc("projects", "doctype", "project_template_task")
frappe.reload_doc("projects", "doctype", "project_template")
frappe.reload_doc("projects", "doctype", "task")
for template_name in frappe.db.sql("""
select
name
from
`tabProject Template` """,
as_dict=1):
template = frappe.get_doc("Project Template", template_name.name)
replace_tasks = False
new_tasks = []
for task in template.tasks:
if task.subject:
replace_tasks = True
new_task = frappe.get_doc(dict(
doctype = "Task",
subject = task.subject,
start = task.start,
duration = task.duration,
task_weight = task.task_weight,
description = task.description,
is_template = 1
)).insert()
new_tasks.append(new_task)
if replace_tasks:
template.tasks = []
for tsk in new_tasks:
template.append("tasks", {
"task": tsk.name,
"subject": tsk.subject
})
template.save()

View File

@ -13,6 +13,7 @@ from frappe.desk.reportview import get_match_cond
from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_email from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_email
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
class Project(Document): class Project(Document):
def get_feed(self): def get_feed(self):
@ -54,18 +55,65 @@ class Project(Document):
self.project_type = template.project_type self.project_type = template.project_type
# create tasks from template # create tasks from template
project_tasks = []
tmp_task_details = []
for task in template.tasks: for task in template.tasks:
frappe.get_doc(dict( template_task_details = frappe.get_doc("Task", task.task)
tmp_task_details.append(template_task_details)
task = self.create_task_from_template(template_task_details)
project_tasks.append(task)
self.dependency_mapping(tmp_task_details, project_tasks)
def create_task_from_template(self, task_details):
return frappe.get_doc(dict(
doctype = 'Task', doctype = 'Task',
subject = task.subject, subject = task_details.subject,
project = self.name, project = self.name,
status = 'Open', status = 'Open',
exp_start_date = add_days(self.expected_start_date, task.start), exp_start_date = self.calculate_start_date(task_details),
exp_end_date = add_days(self.expected_start_date, task.start + task.duration), exp_end_date = self.calculate_end_date(task_details),
description = task.description, description = task_details.description,
task_weight = task.task_weight task_weight = task_details.task_weight,
type = task_details.type,
issue = task_details.issue,
is_group = task_details.is_group
)).insert() )).insert()
def calculate_start_date(self, task_details):
self.start_date = add_days(self.expected_start_date, task_details.start)
self.start_date = update_if_holiday(self.holiday_list, self.start_date)
return self.start_date
def calculate_end_date(self, task_details):
self.end_date = add_days(self.start_date, task_details.duration)
return update_if_holiday(self.holiday_list, self.end_date)
def dependency_mapping(self, template_tasks, project_tasks):
for template_task in template_tasks:
project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0]
project_task = frappe.get_doc("Task", project_task.name)
self.check_depends_on_value(template_task, project_task, project_tasks)
self.check_for_parent_tasks(template_task, project_task, project_tasks)
def check_depends_on_value(self, template_task, project_task, project_tasks):
if template_task.get("depends_on") and not project_task.get("depends_on"):
for child_task in template_task.get("depends_on"):
child_task_subject = frappe.db.get_value("Task", child_task.task, "subject")
corresponding_project_task = list(filter(lambda x: x.subject == child_task_subject, project_tasks))
if len(corresponding_project_task):
project_task.append("depends_on",{
"task": corresponding_project_task[0].name
})
project_task.save()
def check_for_parent_tasks(self, template_task, project_task, project_tasks):
if template_task.get("parent_task") and not project_task.get("parent_task"):
parent_task_subject = frappe.db.get_value("Task", template_task.get("parent_task"), "subject")
corresponding_project_task = list(filter(lambda x: x.subject == parent_task_subject, project_tasks))
if len(corresponding_project_task):
project_task.parent_task = corresponding_project_task[0].name
project_task.save()
def is_row_updated(self, row, existing_task_data, fields): def is_row_updated(self, row, existing_task_data, fields):
if self.get("__islocal") or not existing_task_data: return True if self.get("__islocal") or not existing_task_data: return True
@ -493,3 +541,9 @@ def set_project_status(project, status):
project.status = status project.status = status
project.save() project.save()
def update_if_holiday(holiday_list, date):
holiday_list = holiday_list or get_holiday_list()
while is_holiday(holiday_list, date):
date = add_days(date, 1)
return date

View File

@ -7,60 +7,129 @@ import frappe, unittest
test_records = frappe.get_test_records('Project') test_records = frappe.get_test_records('Project')
test_ignore = ["Sales Order"] test_ignore = ["Sales Order"]
from erpnext.projects.doctype.project_template.test_project_template import get_project_template, make_project_template from erpnext.projects.doctype.project_template.test_project_template import make_project_template
from erpnext.projects.doctype.project.project import set_project_status from erpnext.projects.doctype.project.project import update_if_holiday
from erpnext.projects.doctype.task.test_task import create_task
from frappe.utils import getdate from frappe.utils import getdate, nowdate, add_days
class TestProject(unittest.TestCase): class TestProject(unittest.TestCase):
def test_project_with_template(self): def test_project_with_template_having_no_parent_and_depend_tasks(self):
frappe.db.sql('delete from tabTask where project = "Test Project with Template"') project_name = "Test Project with Template - No Parent and Dependend Tasks"
frappe.delete_doc('Project', 'Test Project with Template') frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
frappe.delete_doc('Project', project_name)
project = get_project('Test Project with Template') task1 = task_exists("Test Template Task with No Parent and Dependency")
if not task1:
task1 = create_task(subject="Test Template Task with No Parent and Dependency", is_template=1, begin=5, duration=3)
tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') template = make_project_template("Test Project Template - No Parent and Dependend Tasks", [task1])
project = get_project(project_name, template)
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks'], dict(project=project.name), order_by='creation asc')
task1 = tasks[0] self.assertEqual(tasks[0].subject, 'Test Template Task with No Parent and Dependency')
self.assertEqual(task1.subject, 'Task 1') self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 5, 3))
self.assertEqual(task1.description, 'Task 1 description') self.assertEqual(len(tasks), 1)
self.assertEqual(getdate(task1.exp_start_date), getdate('2019-01-01'))
self.assertEqual(getdate(task1.exp_end_date), getdate('2019-01-04'))
self.assertEqual(len(tasks), 4) def test_project_template_having_parent_child_tasks(self):
task4 = tasks[3] project_name = "Test Project with Template - Tasks with Parent-Child Relation"
self.assertEqual(task4.subject, 'Task 4') frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06')) frappe.delete_doc('Project', project_name)
def get_project(name): task1 = task_exists("Test Template Task Parent")
template = get_project_template() if not task1:
task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=1)
task2 = task_exists("Test Template Task Child 1")
if not task2:
task2 = create_task(subject="Test Template Task Child 1", parent_task=task1.name, is_template=1, begin=1, duration=3)
task3 = task_exists("Test Template Task Child 2")
if not task3:
task3 = create_task(subject="Test Template Task Child 2", parent_task=task1.name, is_template=1, begin=2, duration=3)
template = make_project_template("Test Project Template - Tasks with Parent-Child Relation", [task1, task2, task3])
project = get_project(project_name, template)
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 1))
self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))
self.assertEqual(tasks[1].parent_task, tasks[0].name)
self.assertEqual(tasks[2].subject, 'Test Template Task Child 2')
self.assertEqual(getdate(tasks[2].exp_end_date), calculate_end_date(project, 2, 3))
self.assertEqual(tasks[2].parent_task, tasks[0].name)
self.assertEqual(len(tasks), 3)
def test_project_template_having_dependent_tasks(self):
project_name = "Test Project with Template - Dependent Tasks"
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
frappe.delete_doc('Project', project_name)
task1 = task_exists("Test Template Task for Dependency")
if not task1:
task1 = create_task(subject="Test Template Task for Dependency", is_template=1, begin=3, duration=1)
task2 = task_exists("Test Template Task with Dependency")
if not task2:
task2 = create_task(subject="Test Template Task with Dependency", depends_on=task1.name, is_template=1, begin=2, duration=2)
template = make_project_template("Test Project with Template - Dependent Tasks", [task1, task2])
project = get_project(project_name, template)
tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name'], dict(project=project.name), order_by='creation asc')
self.assertEqual(tasks[1].subject, 'Test Template Task with Dependency')
self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 2, 2))
self.assertTrue(tasks[1].depends_on_tasks.find(tasks[0].name) >= 0 )
self.assertEqual(tasks[0].subject, 'Test Template Task for Dependency')
self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 3, 1) )
self.assertEqual(len(tasks), 2)
def get_project(name, template):
project = frappe.get_doc(dict( project = frappe.get_doc(dict(
doctype = 'Project', doctype = 'Project',
project_name = name, project_name = name,
status = 'Open', status = 'Open',
project_template = template.name, project_template = template.name,
expected_start_date = '2019-01-01' expected_start_date = nowdate()
)).insert() )).insert()
return project return project
def make_project(args): def make_project(args):
args = frappe._dict(args) args = frappe._dict(args)
if args.project_template_name:
template = make_project_template(args.project_template_name)
else:
template = get_project_template()
project = frappe.get_doc(dict( project = frappe.get_doc(dict(
doctype = 'Project', doctype = 'Project',
project_name = args.project_name, project_name = args.project_name,
status = 'Open', status = 'Open',
project_template = template.name,
expected_start_date = args.start_date expected_start_date = args.start_date
)) ))
if args.project_template_name:
template = make_project_template(args.project_template_name)
project.project_template = template.name
if not frappe.db.exists("Project", args.project_name): if not frappe.db.exists("Project", args.project_name):
project.insert() project.insert()
return project return project
def task_exists(subject):
result = frappe.db.get_list("Task", filters={"subject": subject},fields=["name"])
if not len(result):
return False
return frappe.get_doc("Task", result[0].name)
def calculate_end_date(project, start, duration):
start = add_days(project.expected_start_date, start)
start = update_if_holiday(project.holiday_list, start)
end = add_days(start, duration)
end = update_if_holiday(project.holiday_list, end)
return getdate(end)

View File

@ -5,4 +5,23 @@ frappe.ui.form.on('Project Template', {
// refresh: function(frm) { // refresh: function(frm) {
// } // }
setup: function (frm) {
frm.set_query("task", "tasks", function () {
return {
filters: {
"is_template": 1
}
};
});
}
});
frappe.ui.form.on('Project Template Task', {
task: function (frm, cdt, cdn) {
var row = locals[cdt][cdn];
frappe.db.get_value("Task", row.task, "subject", (value) => {
row.subject = value.subject;
refresh_field("tasks");
});
}
}); });

View File

@ -3,8 +3,28 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
# import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
from frappe.utils import get_link_to_form
class ProjectTemplate(Document): class ProjectTemplate(Document):
pass
def validate(self):
self.validate_dependencies()
def validate_dependencies(self):
for task in self.tasks:
task_details = frappe.get_doc("Task", task.task)
if task_details.depends_on:
for dependency_task in task_details.depends_on:
if not self.check_dependent_task_presence(dependency_task.task):
task_details_format = get_link_to_form("Task",task_details.name)
dependency_task_format = get_link_to_form("Task", dependency_task.task)
frappe.throw(_("Task {0} depends on Task {1}. Please add Task {1} to the Tasks list.").format(frappe.bold(task_details_format), frappe.bold(dependency_task_format)))
def check_dependent_task_presence(self, task):
for task_details in self.tasks:
if task_details.task == task:
return True
return False

View File

@ -5,44 +5,25 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from erpnext.projects.doctype.task.test_task import create_task
class TestProjectTemplate(unittest.TestCase): class TestProjectTemplate(unittest.TestCase):
pass pass
def get_project_template():
if not frappe.db.exists('Project Template', 'Test Project Template'):
frappe.get_doc(dict(
doctype = 'Project Template',
name = 'Test Project Template',
tasks = [
dict(subject='Task 1', description='Task 1 description',
start=0, duration=3),
dict(subject='Task 2', description='Task 2 description',
start=0, duration=2),
dict(subject='Task 3', description='Task 3 description',
start=2, duration=4),
dict(subject='Task 4', description='Task 4 description',
start=3, duration=2),
]
)).insert()
return frappe.get_doc('Project Template', 'Test Project Template')
def make_project_template(project_template_name, project_tasks=[]): def make_project_template(project_template_name, project_tasks=[]):
if not frappe.db.exists('Project Template', project_template_name): if not frappe.db.exists('Project Template', project_template_name):
frappe.get_doc(dict( project_tasks = project_tasks or [
doctype = 'Project Template', create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3),
name = project_template_name, create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2),
tasks = project_tasks or [
dict(subject='Task 1', description='Task 1 description',
start=0, duration=3),
dict(subject='Task 2', description='Task 2 description',
start=0, duration=2),
dict(subject='Task 3', description='Task 3 description',
start=2, duration=4),
dict(subject='Task 4', description='Task 4 description',
start=3, duration=2),
] ]
)).insert() doc = frappe.get_doc(dict(
doctype = 'Project Template',
name = project_template_name
))
for task in project_tasks:
doc.append("tasks",{
"task": task.name
})
doc.insert()
return frappe.get_doc('Project Template', project_template_name) return frappe.get_doc('Project Template', project_template_name)

View File

@ -1,203 +1,41 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-02-18 17:24:41.830096", "creation": "2019-02-18 17:24:41.830096",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"task",
"subject"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "task",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Task",
"columns": 0, "options": "Task",
"reqd": 1
},
{
"columns": 6,
"fieldname": "subject", "fieldname": "subject",
"fieldtype": "Data", "fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Subject"
"label": "Subject",
"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,
"fieldname": "start",
"fieldtype": "Int",
"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": "Begin On (Days)",
"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,
"fieldname": "duration",
"fieldtype": "Int",
"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": "Duration (Days)",
"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,
"fieldname": "task_weight",
"fieldtype": "Float",
"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": "Task Weight",
"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
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"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": "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": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "links": [],
"modified": "2019-02-18 18:30:22.688966", "modified": "2021-01-07 15:13:40.995071",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Project Template Task", "name": "Project Template Task",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -12,6 +12,7 @@
"issue", "issue",
"type", "type",
"is_group", "is_group",
"is_template",
"column_break0", "column_break0",
"status", "status",
"priority", "priority",
@ -22,9 +23,11 @@
"sb_timeline", "sb_timeline",
"exp_start_date", "exp_start_date",
"expected_time", "expected_time",
"start",
"column_break_11", "column_break_11",
"exp_end_date", "exp_end_date",
"progress", "progress",
"duration",
"is_milestone", "is_milestone",
"sb_details", "sb_details",
"description", "description",
@ -112,7 +115,7 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled" "options": "Open\nWorking\nPending Review\nOverdue\nTemplate\nCompleted\nCancelled"
}, },
{ {
"fieldname": "priority", "fieldname": "priority",
@ -360,6 +363,24 @@
"label": "Completed By", "label": "Completed By",
"no_copy": 1, "no_copy": 1,
"options": "User" "options": "User"
},
{
"default": "0",
"fieldname": "is_template",
"fieldtype": "Check",
"label": "Is Template"
},
{
"depends_on": "is_template",
"fieldname": "start",
"fieldtype": "Int",
"label": "Begin On (Days)"
},
{
"depends_on": "is_template",
"fieldname": "duration",
"fieldtype": "Int",
"label": "Duration (Days)"
} }
], ],
"icon": "fa fa-check", "icon": "fa fa-check",
@ -367,7 +388,7 @@
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"max_attachments": 5, "max_attachments": 5,
"modified": "2020-07-03 12:36:04.960457", "modified": "2020-12-28 11:32:58.714991",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Task", "name": "Task",

View File

@ -34,6 +34,7 @@ class Task(NestedSet):
self.validate_progress() self.validate_progress()
self.validate_status() self.validate_status()
self.update_depends_on() self.update_depends_on()
self.validate_dependencies_for_template_task()
def validate_dates(self): 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): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date):
@ -55,6 +56,8 @@ class Task(NestedSet):
validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual")
def validate_status(self): def validate_status(self):
if self.is_template and self.status != "Template":
self.status = "Template"
if self.status!=self.get_db_value("status") and self.status == "Completed": if self.status!=self.get_db_value("status") and self.status == "Completed":
for d in self.depends_on: for d in self.depends_on:
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
@ -72,10 +75,28 @@ class Task(NestedSet):
if self.status == 'Completed': if self.status == 'Completed':
self.progress = 100 self.progress = 100
def validate_dependencies_for_template_task(self):
if self.is_template:
self.validate_parent_template_task()
self.validate_depends_on_tasks()
def validate_parent_template_task(self):
if self.parent_task:
if not frappe.db.get_value("Task", self.parent_task, "is_template"):
parent_task_format = """<a href="#Form/Task/{0}">{0}</a>""".format(self.parent_task)
frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format))
def validate_depends_on_tasks(self):
if self.depends_on:
for task in self.depends_on:
if not frappe.db.get_value("Task", task.task, "is_template"):
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 update_depends_on(self): def update_depends_on(self):
depends_on_tasks = self.depends_on_tasks or "" depends_on_tasks = self.depends_on_tasks or ""
for d in self.depends_on: for d in self.depends_on:
if d.task and not d.task in depends_on_tasks: if d.task and d.task not in depends_on_tasks:
depends_on_tasks += d.task + "," depends_on_tasks += d.task + ","
self.depends_on_tasks = depends_on_tasks self.depends_on_tasks = depends_on_tasks
@ -161,7 +182,7 @@ class Task(NestedSet):
def populate_depends_on(self): def populate_depends_on(self):
if self.parent_task: if self.parent_task:
parent = frappe.get_doc('Task', self.parent_task) parent = frappe.get_doc('Task', self.parent_task)
if not self.name in [row.task for row in parent.depends_on]: if self.name not in [row.task for row in parent.depends_on]:
parent.append("depends_on", { parent.append("depends_on", {
"doctype": "Task Depends On", "doctype": "Task Depends On",
"task": self.name, "task": self.name,

View File

@ -20,7 +20,8 @@ frappe.listview_settings['Task'] = {
"Pending Review": "orange", "Pending Review": "orange",
"Working": "orange", "Working": "orange",
"Completed": "green", "Completed": "green",
"Cancelled": "dark grey" "Cancelled": "dark grey",
"Template": "blue"
} }
return [__(doc.status), colors[doc.status], "status,=," + doc.status]; return [__(doc.status), colors[doc.status], "status,=," + doc.status];
}, },

View File

@ -97,14 +97,19 @@ class TestTask(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue") self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue")
def create_task(subject, start=None, end=None, depends_on=None, project=None, save=True): def create_task(subject, start=None, end=None, depends_on=None, project=None, parent_task=None, is_group=0, is_template=0, begin=0, duration=0, save=True):
if not frappe.db.exists("Task", subject): if not frappe.db.exists("Task", subject):
task = frappe.new_doc('Task') task = frappe.new_doc('Task')
task.status = "Open" task.status = "Open"
task.subject = subject task.subject = subject
task.exp_start_date = start or nowdate() task.exp_start_date = start or nowdate()
task.exp_end_date = end or nowdate() task.exp_end_date = end or nowdate()
task.project = project or "_Test Project" task.project = project or None if is_template else "_Test Project"
task.is_template = is_template
task.start = begin
task.duration = duration
task.is_group = is_group
task.parent_task = parent_task
if save: if save:
task.save() task.save()
else: else:
@ -116,5 +121,4 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, sa
}) })
if save: if save:
task.save() task.save()
return task return task