From 93583faaf0d5fbfdfc1681be9c44f2b9d485cf74 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 22 Apr 2015 16:28:30 +0530 Subject: [PATCH 01/10] depends-on added to task, circular reference validation added --- erpnext/projects/doctype/task/task.json | 10 +++++++++- erpnext/projects/doctype/task/task.py | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index fc604c264c..ad1ad01c4e 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -27,6 +27,14 @@ "options": "Project", "permlevel": 0 }, + { + "fieldname": "depends_on", + "fieldtype": "Link", + "label": "Depends on (Task)", + "options": "Task", + "permlevel": 0, + "precision": "" + }, { "fieldname": "column_break0", "fieldtype": "Column Break", @@ -249,7 +257,7 @@ "idx": 1, "istable": 0, "max_attachments": 5, - "modified": "2015-04-14 07:56:24.481667", + "modified": "2015-04-22 04:58:30.865304", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index a03340f7de..589af2b92d 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -27,6 +27,7 @@ class Task(Document): def validate(self): self.validate_dates() + self.validate_depends_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): @@ -68,6 +69,21 @@ class Task(Document): project.flags.dont_sync_tasks = True project.update_costing() project.save() + + def validate_depends_on(self): + if not self.depends_on: + return + task_list = [self.name] + task = self.depends_on + while task: + task = self.check_recursion(task, task_list) + + def check_recursion(self, task, task_list): + if task in task_list: + frappe.throw("Circular Reference Error") + else : + task_list.append(task) + return frappe.db.get_value("Task", task, "depends_on") @frappe.whitelist() def get_events(start, end, filters=None): From 46d859e6940360532c5e2861082f45e80e798bf0 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 23 Apr 2015 13:05:09 +0530 Subject: [PATCH 02/10] re-schedule dependaable task logic added --- erpnext/projects/doctype/task/task.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 589af2b92d..25369a7f6e 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -6,6 +6,7 @@ import frappe, json from frappe.utils import getdate from frappe import _ +from data import date_diff, add_days from frappe.model.document import Document @@ -28,6 +29,7 @@ class Task(Document): def validate(self): self.validate_dates() self.validate_depends_on() + self.reschedule_depending_task() 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): @@ -84,6 +86,14 @@ class Task(Document): else : task_list.append(task) return frappe.db.get_value("Task", task, "depends_on") + + def reschedule_depending_task(self): + for task_name in frappe.db.sql("select name from `tabTask` where depends_on = %s", self.name, as_dict=1): + task = frappe.get_doc("Task", task_name.name) + task_duration = date_diff(task.exp_end_date, task.exp_start_date) + task.exp_start_date = add_days(self.act_end_date if self.act_end_date else self.exp_end_date, 1) + task.exp_end_date = add_days(task.exp_start_date, task_duration) + task.save() @frappe.whitelist() def get_events(start, end, filters=None): From fa381c7a6ccfd2cb0d60c1b220888b69f7db81be Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 23 Apr 2015 13:12:18 +0530 Subject: [PATCH 03/10] task-rescheduling - handled exception if date is None --- erpnext/projects/doctype/task/task.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 25369a7f6e..4d1065d36b 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -90,10 +90,11 @@ class Task(Document): def reschedule_depending_task(self): for task_name in frappe.db.sql("select name from `tabTask` where depends_on = %s", self.name, as_dict=1): task = frappe.get_doc("Task", task_name.name) - task_duration = date_diff(task.exp_end_date, task.exp_start_date) - task.exp_start_date = add_days(self.act_end_date if self.act_end_date else self.exp_end_date, 1) - task.exp_end_date = add_days(task.exp_start_date, task_duration) - task.save() + if task.exp_start_date and task.exp_end_date and (self.exp_end_date or self.act_end_date): + task_duration = date_diff(task.exp_end_date, task.exp_start_date) + task.exp_start_date = add_days(self.act_end_date if self.act_end_date else self.exp_end_date, 1) + task.exp_end_date = add_days(task.exp_start_date, task_duration) + task.save() @frappe.whitelist() def get_events(start, end, filters=None): From 3da42fcdf97a95011742894a2e0fc8f183fd3500 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 23 Apr 2015 16:12:45 +0530 Subject: [PATCH 04/10] fixed import errors --- erpnext/projects/doctype/task/task.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 4d1065d36b..b52e2d8f5e 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -4,10 +4,8 @@ from __future__ import unicode_literals import frappe, json -from frappe.utils import getdate +from frappe.utils import getdate, date_diff, add_days from frappe import _ -from data import date_diff, add_days - from frappe.model.document import Document From 1fa8ed8a1f75568d08a9ebc1c5defbc9bb871cd5 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 23 Apr 2015 17:35:44 +0530 Subject: [PATCH 05/10] Test Cases Added, fixes in task --- erpnext/projects/doctype/task/task.py | 8 +- erpnext/projects/doctype/task/test_task.py | 92 ++++++++++++++++++- .../doctype/time_log/test_time_log.py | 1 - 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index b52e2d8f5e..6b16f02cce 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -9,6 +9,8 @@ from frappe import _ from frappe.model.document import Document +class ReferenceError(frappe.ValidationError): pass + class Task(Document): def get_feed(self): return '{0}: {1}'.format(_(self.status), self.subject) @@ -53,8 +55,8 @@ class Task(Document): def update_time_and_costing(self): tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount, - sum(hours) as time from `tabTime Log` where project = %s and task = %s and docstatus=1""", - (self.project, self.name),as_dict=1)[0] + sum(hours) as time from `tabTime Log` where task = %s and docstatus=1""" + ,self.name, as_dict=1)[0] if self.status == "Open": self.status = "Working" self.total_costing_amount= tl.total_costing_amount @@ -80,7 +82,7 @@ class Task(Document): def check_recursion(self, task, task_list): if task in task_list: - frappe.throw("Circular Reference Error") + frappe.throw("Circular Reference Error", ReferenceError) else : task_list.append(task) return frappe.db.get_value("Task", task, "depends_on") diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 62e560fbe7..f1038b47f4 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -1,7 +1,95 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - - import frappe +import unittest +from frappe.utils import getdate + test_records = frappe.get_test_records('Task') + +from erpnext.projects.doctype.task.task import ReferenceError + +class TestTask(unittest.TestCase): + def test_circular_refereence(self): + task1 = frappe.new_doc('Task') + task1.update({ + "status": "Open", + "subject": "_Test Task 3" + }) + task1.save() + + task2 = frappe.new_doc('Task') + task2.update({ + "status": "Open", + "subject": "_Test Task 4", + "depends_on": task1.name + }) + task2.save() + + task3 = frappe.new_doc('Task') + task3.update({ + "status": "Open", + "subject": "_Test Task 5", + "depends_on": task2.name + }) + task3.save() + + task1.update({ + "depends_on": task3.name + }) + self.assertRaises(ReferenceError, task1.save) + + def test_reschedule_depending_task(self): + task1 = frappe.new_doc('Task') + task1.update({ + "status": "Open", + "subject": "_Test Task 6", + "exp_start_date": "2015-1-1", + "exp_end_date": "2015-1-10" + }) + task1.save() + + task2 = frappe.new_doc('Task') + task2.update({ + "status": "Open", + "subject": "_Test Task 7", + "exp_start_date": "2015-1-11", + "exp_end_date": "2015-1-15", + "depends_on": task1.name + }) + task2.save() + + task3 = frappe.new_doc('Task') + task3.update({ + "status": "Open", + "subject": "_Test Task 5", + "exp_start_date": "2015-1-16", + "exp_end_date": "2015-1-18", + "depends_on": task2.name + }) + task3.save() + + task1.update({ + "exp_end_date": "2015-1-20" + }) + task1.save() + + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21')) + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25')) + + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26')) + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28')) + + time_log = frappe.new_doc('Time Log') + time_log.update({ + "from_time": "2015-1-1", + "to_time": "2015-1-22", + "task": task1.name + }) + time_log.submit() + + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-23')) + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-27')) + + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-28')) + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-30')) \ No newline at end of file diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index 3d9e0be5f9..9b43b0dffe 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -1,6 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals from __future__ import unicode_literals import frappe From c8f0c25088f1d8f936a3f25b884d4e4d4179c777 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 23 Apr 2015 18:49:11 +0530 Subject: [PATCH 06/10] referenceError name changed to circularReferenceError --- erpnext/projects/doctype/task/task.py | 4 ++-- erpnext/projects/doctype/task/test_task.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 6b16f02cce..2a7aedc382 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document -class ReferenceError(frappe.ValidationError): pass +class CircularReferenceError(frappe.ValidationError): pass class Task(Document): def get_feed(self): @@ -82,7 +82,7 @@ class Task(Document): def check_recursion(self, task, task_list): if task in task_list: - frappe.throw("Circular Reference Error", ReferenceError) + frappe.throw("Circular Reference Error", CircularReferenceError) else : task_list.append(task) return frappe.db.get_value("Task", task, "depends_on") diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index f1038b47f4..30a64f26c2 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -7,7 +7,7 @@ from frappe.utils import getdate test_records = frappe.get_test_records('Task') -from erpnext.projects.doctype.task.task import ReferenceError +from erpnext.projects.doctype.task.task import CircularReferenceError class TestTask(unittest.TestCase): def test_circular_refereence(self): @@ -37,7 +37,7 @@ class TestTask(unittest.TestCase): task1.update({ "depends_on": task3.name }) - self.assertRaises(ReferenceError, task1.save) + self.assertRaises(CircularReferenceError, task1.save) def test_reschedule_depending_task(self): task1 = frappe.new_doc('Task') From 350943128abec8bef9598975ce8854c873e4b0e3 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 27 Apr 2015 15:23:19 +0530 Subject: [PATCH 07/10] fixes in Pull request --- erpnext/projects/doctype/task/task.py | 20 +++++++++++--------- erpnext/projects/doctype/task/test_task.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 2a7aedc382..2ffa1bf364 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -29,7 +29,7 @@ class Task(Document): def validate(self): self.validate_dates() self.validate_depends_on() - self.reschedule_depending_task() + self.reschedule_dependent_task() 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): @@ -87,14 +87,16 @@ class Task(Document): task_list.append(task) return frappe.db.get_value("Task", task, "depends_on") - def reschedule_depending_task(self): - for task_name in frappe.db.sql("select name from `tabTask` where depends_on = %s", self.name, as_dict=1): - task = frappe.get_doc("Task", task_name.name) - if task.exp_start_date and task.exp_end_date and (self.exp_end_date or self.act_end_date): - task_duration = date_diff(task.exp_end_date, task.exp_start_date) - task.exp_start_date = add_days(self.act_end_date if self.act_end_date else self.exp_end_date, 1) - task.exp_end_date = add_days(task.exp_start_date, task_duration) - task.save() + def reschedule_dependent_task(self): + end_date = self.exp_end_date or self.act_end_date + if end_date: + for task_name in frappe.db.sql("select name from `tabTask` where depends_on = %s", self.name, as_dict=1): + task = frappe.get_doc("Task", task_name.name) + if task.exp_start_date and task.exp_end_date and task.exp_start_date < end_date and task.status == "Open" : + task_duration = date_diff(task.exp_end_date, task.exp_start_date) + task.exp_start_date = add_days(end_date, 1) + task.exp_end_date = add_days(task.exp_start_date, task_duration) + task.save() @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 30a64f26c2..bc5ee14598 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -39,7 +39,7 @@ class TestTask(unittest.TestCase): }) self.assertRaises(CircularReferenceError, task1.save) - def test_reschedule_depending_task(self): + def test_reschedule_dependent_task(self): task1 = frappe.new_doc('Task') task1.update({ "status": "Open", From b3b373d0ace4325369a6f581699827cbd23a931d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 27 Apr 2015 15:59:41 +0530 Subject: [PATCH 08/10] fixes in Test Cases --- erpnext/projects/doctype/task/task.py | 2 +- erpnext/projects/doctype/task/test_task.py | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 2ffa1bf364..cde23e7db6 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -92,7 +92,7 @@ class Task(Document): if end_date: for task_name in frappe.db.sql("select name from `tabTask` where depends_on = %s", self.name, as_dict=1): task = frappe.get_doc("Task", task_name.name) - if task.exp_start_date and task.exp_end_date and task.exp_start_date < end_date and task.status == "Open" : + if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open" : task_duration = date_diff(task.exp_end_date, task.exp_start_date) task.exp_start_date = add_days(end_date, 1) task.exp_end_date = add_days(task.exp_start_date, task_duration) diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index bc5ee14598..aea6a2515e 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -83,13 +83,22 @@ class TestTask(unittest.TestCase): time_log = frappe.new_doc('Time Log') time_log.update({ "from_time": "2015-1-1", - "to_time": "2015-1-22", + "to_time": "2015-1-20", "task": task1.name }) time_log.submit() - self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-23')) - self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-27')) + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21')) + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25')) - self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-28')) - self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-30')) \ No newline at end of file + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26')) + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28')) + + time_log.cancel() + + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21')) + self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25')) + + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26')) + self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28')) + \ No newline at end of file From b4a298ddd4f99d69a460801c180bb108123329f1 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 30 Apr 2015 20:32:42 +0530 Subject: [PATCH 09/10] multiple dependant tasks added --- .../doctype/dependent_task/__init__.py | 0 .../dependent_task/dependent_task.json | 50 ++++++++ .../doctype/dependent_task/dependent_task.py | 10 ++ erpnext/projects/doctype/task/task.js | 1 + erpnext/projects/doctype/task/task.json | 28 +++-- erpnext/projects/doctype/task/task.py | 42 ++++--- erpnext/projects/doctype/task/test_task.py | 116 ++++++++++++------ .../doctype/task_depends_on/__init__.py | 0 .../task_depends_on/task_depends_on.json | 66 ++++++++++ .../task_depends_on/task_depends_on.py | 10 ++ 10 files changed, 257 insertions(+), 66 deletions(-) create mode 100644 erpnext/projects/doctype/dependent_task/__init__.py create mode 100644 erpnext/projects/doctype/dependent_task/dependent_task.json create mode 100644 erpnext/projects/doctype/dependent_task/dependent_task.py create mode 100644 erpnext/projects/doctype/task_depends_on/__init__.py create mode 100644 erpnext/projects/doctype/task_depends_on/task_depends_on.json create mode 100644 erpnext/projects/doctype/task_depends_on/task_depends_on.py diff --git a/erpnext/projects/doctype/dependent_task/__init__.py b/erpnext/projects/doctype/dependent_task/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/doctype/dependent_task/dependent_task.json b/erpnext/projects/doctype/dependent_task/dependent_task.json new file mode 100644 index 0000000000..c649b53b17 --- /dev/null +++ b/erpnext/projects/doctype/dependent_task/dependent_task.json @@ -0,0 +1,50 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2015-04-29 04:52:48.868079", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "task", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Task", + "no_copy": 0, + "options": "Task", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-04-29 04:54:36.024844", + "modified_by": "Administrator", + "module": "Projects", + "name": "Dependent Task", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/projects/doctype/dependent_task/dependent_task.py b/erpnext/projects/doctype/dependent_task/dependent_task.py new file mode 100644 index 0000000000..90a96ac1b7 --- /dev/null +++ b/erpnext/projects/doctype/dependent_task/dependent_task.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class DependentTask(Document): + pass diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 975633186b..d9a611eed6 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -40,6 +40,7 @@ erpnext.projects.Task = frappe.ui.form.Controller.extend({ } }); +cur_frm.add_fetch('task', 'subject', 'subject'); cur_frm.cscript = new erpnext.projects.Task({frm: cur_frm}); diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index ad1ad01c4e..ddcc48b2d3 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -27,14 +27,6 @@ "options": "Project", "permlevel": 0 }, - { - "fieldname": "depends_on", - "fieldtype": "Link", - "label": "Depends on (Task)", - "options": "Task", - "permlevel": 0, - "precision": "" - }, { "fieldname": "column_break0", "fieldtype": "Column Break", @@ -86,12 +78,26 @@ "width": "300px" }, { - "fieldname": "time_and_budget", + "fieldname": "section_break", "fieldtype": "Section Break", - "label": "", + "label": "Depends On", "oldfieldtype": "Section Break", "permlevel": 0 }, + { + "fieldname": "depends_on", + "fieldtype": "Table", + "label": "depends_on", + "options": "Task Depends On", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "exp_start_date", "fieldtype": "Date", @@ -257,7 +263,7 @@ "idx": 1, "istable": 0, "max_attachments": 5, - "modified": "2015-04-22 04:58:30.865304", + "modified": "2015-04-30 05:48:55.176993", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index cde23e7db6..a232be1604 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, json -from frappe.utils import getdate, date_diff, add_days +from frappe.utils import getdate, date_diff, add_days, cstr from frappe import _ from frappe.model.document import Document @@ -28,8 +28,6 @@ class Task(Document): def validate(self): self.validate_dates() - self.validate_depends_on() - self.reschedule_dependent_task() 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): @@ -39,6 +37,8 @@ class Task(Document): frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) def on_update(self): + self.check_recursion() + self.reschedule_dependent_tasks() self.update_percentage() self.update_project() @@ -72,30 +72,34 @@ class Task(Document): project.update_costing() project.save() - def validate_depends_on(self): - if not self.depends_on: - return - task_list = [self.name] - task = self.depends_on - while task: - task = self.check_recursion(task, task_list) + def check_recursion(self): + if self.flags.recursion_check: return + check_list = [['task', 'parent'], ['parent', 'task']] + for d in check_list: + task_list, count = [self.name], 0 + while (len(task_list) > count ): + tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " % + (d[0], d[1], '%s'), cstr(task_list[count])) + count = count + 1 + for b in tasks: + if b[0] == self.name: + frappe.throw(_("Circular Reference Error"), CircularReferenceError) + if b[0]: + task_list.append(b[0]) + if count == 15: + break - def check_recursion(self, task, task_list): - if task in task_list: - frappe.throw("Circular Reference Error", CircularReferenceError) - else : - task_list.append(task) - return frappe.db.get_value("Task", task, "depends_on") - - def reschedule_dependent_task(self): + def reschedule_dependent_tasks(self): end_date = self.exp_end_date or self.act_end_date if end_date: - for task_name in frappe.db.sql("select name from `tabTask` where depends_on = %s", self.name, as_dict=1): + for task_name in frappe.db.sql("select name from `tabTask` as parent where %s in \ + (select task from `tabTask Depends On` as child where parent.name = child.parent )", self.name, as_dict=1): task = frappe.get_doc("Task", task_name.name) if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open" : task_duration = date_diff(task.exp_end_date, task.exp_start_date) task.exp_start_date = add_days(end_date, 1) task.exp_end_date = add_days(task.exp_start_date, task_duration) + task.flags.recursion_check = True task.save() @frappe.whitelist() diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index aea6a2515e..8880763fa2 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -5,45 +5,17 @@ import frappe import unittest from frappe.utils import getdate -test_records = frappe.get_test_records('Task') +# test_records = frappe.get_test_records('Task') from erpnext.projects.doctype.task.task import CircularReferenceError class TestTask(unittest.TestCase): - def test_circular_refereence(self): + def test_circular_reference(self): + task1 = frappe.new_doc('Task') task1.update({ "status": "Open", - "subject": "_Test Task 3" - }) - task1.save() - - task2 = frappe.new_doc('Task') - task2.update({ - "status": "Open", - "subject": "_Test Task 4", - "depends_on": task1.name - }) - task2.save() - - task3 = frappe.new_doc('Task') - task3.update({ - "status": "Open", - "subject": "_Test Task 5", - "depends_on": task2.name - }) - task3.save() - - task1.update({ - "depends_on": task3.name - }) - self.assertRaises(CircularReferenceError, task1.save) - - def test_reschedule_dependent_task(self): - task1 = frappe.new_doc('Task') - task1.update({ - "status": "Open", - "subject": "_Test Task 6", + "subject": "_Test Task 1", "exp_start_date": "2015-1-1", "exp_end_date": "2015-1-10" }) @@ -52,20 +24,92 @@ class TestTask(unittest.TestCase): task2 = frappe.new_doc('Task') task2.update({ "status": "Open", - "subject": "_Test Task 7", + "subject": "_Test Task 2", "exp_start_date": "2015-1-11", "exp_end_date": "2015-1-15", - "depends_on": task1.name + "depends_on":[ + { + "task": task1.name + } + ] }) task2.save() task3 = frappe.new_doc('Task') task3.update({ "status": "Open", - "subject": "_Test Task 5", + "subject": "_Test Task 2", + "exp_start_date": "2015-1-11", + "exp_end_date": "2015-1-15", + "depends_on":[ + { + "task": task2.name + } + ] + }) + task3.save() + + task1.append("depends_on", { + "task": task3.name + }) + self.assertRaises(CircularReferenceError, task1.save) + + task1.set("depends_on", []) + task1.save() + + task4 = frappe.new_doc('Task') + task4.update({ + "status": "Open", + "subject": "_Test Task 1", + "exp_start_date": "2015-1-1", + "exp_end_date": "2015-1-15", + "depends_on":[ + { + "task": task1.name + } + ] + }) + task4.save() + + task3.append("depends_on", { + "task": task4.name + }) + + def test_reschedule_dependent_task(self): + task1 = frappe.new_doc('Task') + task1.update({ + "status": "Open", + "subject": "_Test Task 1", + "exp_start_date": "2015-1-1", + "exp_end_date": "2015-1-10" + }) + task1.save() + + task2 = frappe.new_doc('Task') + task2.update({ + "status": "Open", + "subject": "_Test Task 2", + "exp_start_date": "2015-1-11", + "exp_end_date": "2015-1-15", + "depends_on":[ + { + "task": task1.name + } + ] + }) + task2.save() + + task3 = frappe.new_doc('Task') + task3.update({ + "status": "Open", + "subject": "_Test Task 3", "exp_start_date": "2015-1-16", "exp_end_date": "2015-1-18", - "depends_on": task2.name + "depends_on":[ + { + "task": task2.name + } + ] }) task3.save() diff --git a/erpnext/projects/doctype/task_depends_on/__init__.py b/erpnext/projects/doctype/task_depends_on/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.json b/erpnext/projects/doctype/task_depends_on/task_depends_on.json new file mode 100644 index 0000000000..7a960c1c41 --- /dev/null +++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.json @@ -0,0 +1,66 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2015-04-29 04:52:48.868079", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "task", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Task", + "no_copy": 0, + "options": "Task", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "subject", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Subject", + "options": "", + "permlevel": 0, + "precision": "", + "read_only": 1 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-04-30 05:52:16.250948", + "modified_by": "Administrator", + "module": "Projects", + "name": "Task Depends On", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.py b/erpnext/projects/doctype/task_depends_on/task_depends_on.py new file mode 100644 index 0000000000..723a0fc339 --- /dev/null +++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class TaskDependsOn(Document): + pass From 58bf6c5950ea924ec1262b76a60a269a704c2eb9 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 13 May 2015 19:29:47 +0530 Subject: [PATCH 10/10] Update task.py --- erpnext/projects/doctype/task/task.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index a232be1604..456c40a97e 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -73,7 +73,7 @@ class Task(Document): project.save() def check_recursion(self): - if self.flags.recursion_check: return + if self.flags.ignore_recursion_check: return check_list = [['task', 'parent'], ['parent', 'task']] for d in check_list: task_list, count = [self.name], 0 @@ -99,7 +99,7 @@ class Task(Document): task_duration = date_diff(task.exp_end_date, task.exp_start_date) task.exp_start_date = add_days(end_date, 1) task.exp_end_date = add_days(task.exp_start_date, task_duration) - task.flags.recursion_check = True + task.flags.ignore_recursion_check = True task.save() @frappe.whitelist()