From c32ac223ed181d3a1a122775b93d6e4672d2e38f Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 7 Dec 2020 14:53:15 +0530 Subject: [PATCH 01/29] feat: adding task field in project template --- .../project_template_task.json | 195 ++---------------- erpnext/projects/doctype/task/task.json | 21 +- 2 files changed, 32 insertions(+), 184 deletions(-) diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.json b/erpnext/projects/doctype/project_template_task/project_template_task.json index 8644d897bb..80c510db1b 100644 --- a/erpnext/projects/doctype/project_template_task/project_template_task.json +++ b/erpnext/projects/doctype/project_template_task/project_template_task.json @@ -1,203 +1,32 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, + "actions": [], "creation": "2019-02-18 17:24:41.830096", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "task" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subject", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, + "fieldname": "task", + "fieldtype": "Link", "in_list_view": 1, - "in_standard_filter": 0, - "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 + "label": "Task", + "options": "Task", + "reqd": 1 } ], - "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, - "max_attachments": 0, - "modified": "2019-02-18 18:30:22.688966", + "links": [], + "modified": "2020-12-07 13:28:40.961810", "modified_by": "Administrator", "module": "Projects", "name": "Project Template Task", - "name_case": "", "owner": "Administrator", "permissions": [], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 27f1a71a52..a9e3d9bc0f 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -12,6 +12,7 @@ "issue", "type", "is_group", + "is_template", "column_break0", "status", "priority", @@ -22,9 +23,11 @@ "sb_timeline", "exp_start_date", "expected_time", + "start", "column_break_11", "exp_end_date", "progress", + "duration", "is_milestone", "sb_details", "description", @@ -360,6 +363,22 @@ "label": "Completed By", "no_copy": 1, "options": "User" + }, + { + "default": "0", + "fieldname": "is_template", + "fieldtype": "Check", + "label": "Is Template" + }, + { + "fieldname": "start", + "fieldtype": "Int", + "label": "Begin On (Days)" + }, + { + "fieldname": "duration", + "fieldtype": "Int", + "label": "Duration (Days)" } ], "icon": "fa fa-check", @@ -367,7 +386,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2020-07-03 12:36:04.960457", + "modified": "2020-12-07 13:26:53.614689", "modified_by": "Administrator", "module": "Projects", "name": "Task", From c82b52855cf43774e78d54a7fcd41c62884ac47a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 8 Dec 2020 11:07:59 +0530 Subject: [PATCH 02/29] fix: template filter for tasks --- .../doctype/project_template/project_template.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/projects/doctype/project_template/project_template.js b/erpnext/projects/doctype/project_template/project_template.js index d7a876dfbd..acc6849f2b 100644 --- a/erpnext/projects/doctype/project_template/project_template.js +++ b/erpnext/projects/doctype/project_template/project_template.js @@ -5,4 +5,13 @@ frappe.ui.form.on('Project Template', { // refresh: function(frm) { // } + setup: function (frm) { + me.frm.set_query("task", "tasks", function (doc, cdt, cdn) { + return { + filters: { + "is_template": 1 + } + } + }); + } }); From 811629a9616f411eb40b0d0adb301dc5f0ff8cf3 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 9 Dec 2020 15:52:05 +0530 Subject: [PATCH 03/29] feat: tasks in project template and tests --- erpnext/projects/doctype/project/project.py | 38 ++++++-- .../projects/doctype/project/test_project.py | 92 +++++++++++++++---- .../project_template/test_project_template.py | 49 +++++----- erpnext/projects/doctype/task/test_task.py | 6 +- 4 files changed, 126 insertions(+), 59 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 5bbd29c4c4..04a0fb6c4f 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -55,16 +55,34 @@ class Project(Document): # create tasks from template for task in template.tasks: - frappe.get_doc(dict( - doctype = 'Task', - subject = task.subject, - project = self.name, - status = 'Open', - exp_start_date = add_days(self.expected_start_date, task.start), - exp_end_date = add_days(self.expected_start_date, task.start + task.duration), - description = task.description, - task_weight = task.task_weight - )).insert() + template_task_details = frappe.get_doc("Task", task.task) + project_task = self.create_task_from_template(template_task_details) + + if template_task_details.depends_on: + for child_task in template_task_details.depends_on: + child_task_details = frappe.get_doc("Task",child_task.task) + self.create_task_from_template(child_task_details, project_task) + + def create_task_from_template(self, task_details, project_task=None): + doc = frappe.get_doc(dict( + doctype = 'Task', + subject = task_details.subject, + project = self.name, + status = 'Open', + exp_start_date = add_days(self.expected_start_date, task_details.start), + exp_end_date = add_days(self.expected_start_date, task_details.start + task_details.duration), + description = task_details.description, + task_weight = task_details.task_weight, + type = task_details.type, + issue = task_details.issue, + is_group = task_details.is_group + )) + if task_details.parent_task and project_task: + doc.parent_task = project_task.name + if not task_details.is_group: + doc.depends_on = task_details.depends_on + doc.insert() + return doc def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: return True diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 0c4f6f1bdf..ce95b05614 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -7,33 +7,87 @@ import frappe, unittest test_records = frappe.get_test_records('Project') 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 frappe.utils import getdate +from erpnext.projects.doctype.task.test_task import create_task +from frappe.utils import getdate, nowdate, add_days class TestProject(unittest.TestCase): - def test_project_with_template(self): - frappe.db.sql('delete from tabTask where project = "Test Project with Template"') - frappe.delete_doc('Project', 'Test Project with Template') + def test_project_with_template_having_no_parent_and_depend_tasks(self): + """ + Test Action: Basic Test of a Project created from template. The template has a single task. + """ + frappe.db.sql('delete from tabTask where project = "Test Project with Templ - no parent and dependend tasks"') + frappe.delete_doc('Project', 'Test Project with Templ - no parent and dependend tasks') - project = get_project('Test Project with Template') + if not frappe.db.exists("Task", "Test Temp Task with no parent and dependency"): + task1 = create_task(subject="Test Temp Task with no parent and dependency", is_template=1, begin=0, duration=3) + template = make_project_template("Test Project Template - no parent and dependend tasks", [task1]) + project = get_project("Test Project with Templ - no parent and dependend tasks", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - task1 = tasks[0] - self.assertEqual(task1.subject, 'Task 1') - self.assertEqual(task1.description, 'Task 1 description') - self.assertEqual(getdate(task1.exp_start_date), getdate('2019-01-01')) - self.assertEqual(getdate(task1.exp_end_date), getdate('2019-01-04')) + self.assertEqual(task[0].subject, 'Test Temp Task with no parent and dependency') + self.assertEqual(getdate(task[0].exp_end_date), add_days(nowdate() + 0 + 3)) + self.assertEqual(len(tasks), 1) - self.assertEqual(len(tasks), 4) - task4 = tasks[3] - self.assertEqual(task4.subject, 'Task 4') - self.assertEqual(getdate(task4.exp_end_date), getdate('2019-01-06')) + def test_project_template_having_parent_child_tasks(self): -def get_project(name): - template = get_project_template() + frappe.db.sql('delete from tabTask where project = "Test Project with Templ - tasks with parent-child"') + frappe.delete_doc('Project', 'Test Project with Templ - tasks with parent-child') + + if not frappe.db.exists("Task", "Test Temp Task parent"): + task1 = create_task(subject="Test Temp Task parent", is_group=1, is_template=1, begin=1, duration=1) + + if not frappe.db.exists("Task", "Test Temp Task child 1"): + task2 = create_task(subject="Test Temp Task child 1", parent_task=task1.name, is_template=1, begin=1, duration=3) + + if not frappe.db.exists("Task", "Test Temp Task child 2"): + task3 = create_task(subject="Test Temp 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", [task1, task2, task3]) + project = get_project("Test Project with Templ - tasks with parent-child", template) + tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') + + self.assertEqual(task[0].subject, 'Test Temp Task parent') + self.assertEqual(getdate(task[0].exp_end_date), add_days(nowdate()+ 1 + 1)) + print(task[0].depends_on) + + self.assertEqual(task[1].subject, 'Test Temp Task child 1') + self.assertEqual(getdate(task[1].exp_end_date), add_days(nowdate()+ 1 + 3)) + self.assertEqual(task[1].parent_task, task[0].name) + + self.assertEqual(task[2].subject, 'Test Temp Task child 2') + self.assertEqual(getdate(task[2].exp_end_date), add_days(nowdate()+ 2 + 3)) + self.assertEqual(task[2].parent_task, task[0].name) + + self.assertEqual(len(tasks), 3) + + def test_project_template_having_dependent_tasks(self): + + frappe.db.sql('delete from tabTask where project = "Test Project with Templ - dependent tasks"') + frappe.delete_doc('Project', 'Test Project with Templ - dependent tasks') + + if not frappe.db.exists("Task", "Test Temp Task for dependency"): + task1 = create_task(subject="Test Temp Task for dependency", is_template=1, begin=3, duration=1) + + if not frappe.db.exists("Task", "Test Temp Task with dependency"): + task2 = create_task(subject="Test Temp Task with dependency", depends_on=task1.name, is_template=1, begin=2, duration=2) + + template = make_project_template("Test Project Template - tasks with parent-child", [task1, task2]) + project = get_project("Test Project with Templ - tasks with parent-child", template) + tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') + + self.assertEqual(task[0].subject, 'Test Temp Task for dependency') + self.assertEqual(getdate(task[0].exp_end_date), add_days(nowdate()+ 3 + 1)) + + self.assertEqual(task[1].subject, 'Test Temp Task with dependency') + self.assertEqual(getdate(task[1].exp_end_date), add_days(nowdate()+ 2 + 2)) + self.assertEqual(task[1].depends_on, task[0].name) + + self.assertEqual(len(tasks), 2) + +def get_project(name, template): project = frappe.get_doc(dict( doctype = 'Project', @@ -49,8 +103,6 @@ def make_project(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( doctype = 'Project', diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 2c5831a5dc..dd98d02c02 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -5,44 +5,37 @@ from __future__ import unicode_literals import frappe import unittest +from erpnext.projects.doctype.task.test_task import create_task class TestProjectTemplate(unittest.TestCase): 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 get_project_template(project_template_name="Test Project Template", project_tasks=[]): if not frappe.db.exists('Project Template', project_template_name): frappe.get_doc(dict( doctype = 'Project Template', name = project_template_name, 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), + create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), + create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), + create_task(subject="_Test Template Task 3", is_template=1, begin=2, duration=4), + create_task(subject="_Test Template Task 4", is_template=1, begin=3, duration=2), ] )).insert() + return frappe.get_doc('Project Template', project_template_name) + +def make_project_template(project_template_name, project_tasks=[]): + if not frappe.db.exists('Project Template', project_template_name): + doc = frappe.get_doc(dict( + doctype = 'Project Template', + name = project_template_name, + tasks = project_tasks or [ + create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), + create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), + ] + )) + print("doc",doc.tasks) + doc.insert() + return frappe.get_doc('Project Template', project_template_name) \ No newline at end of file diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 47a28fd111..181a2dc316 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -97,7 +97,7 @@ class TestTask(unittest.TestCase): 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): task = frappe.new_doc('Task') task.status = "Open" @@ -105,6 +105,10 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, sa task.exp_start_date = start or nowdate() task.exp_end_date = end or nowdate() task.project = project or "_Test Project" + task.is_template = is_template, + task.start = begin + task.duration = duration, + task.is_group = is_group if save: task.save() else: From f936e8a334bb8abab78d9938d6808d9e8688a6ee Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 9 Dec 2020 17:09:22 +0530 Subject: [PATCH 04/29] fix: sider issues fixed --- .../projects/doctype/project/test_project.py | 33 +++++++++---------- .../project_template/project_template.js | 4 +-- .../project_template/test_project_template.py | 6 ++-- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index ce95b05614..52f877b8b7 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -27,8 +27,8 @@ class TestProject(unittest.TestCase): project = get_project("Test Project with Templ - no parent and dependend tasks", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - self.assertEqual(task[0].subject, 'Test Temp Task with no parent and dependency') - self.assertEqual(getdate(task[0].exp_end_date), add_days(nowdate() + 0 + 3)) + self.assertEqual(tasks[0].subject, 'Test Temp Task with no parent and dependency') + self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate() + 0 + 3)) self.assertEqual(len(tasks), 1) def test_project_template_having_parent_child_tasks(self): @@ -48,18 +48,17 @@ class TestProject(unittest.TestCase): template = make_project_template("Test Project Template - tasks with parent-child", [task1, task2, task3]) project = get_project("Test Project with Templ - tasks with parent-child", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') + print(tasks, type(tasks), len(tasks)) + self.assertEqual(tasks[0].subject, 'Test Temp Task parent') + self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate()+ 1 + 1)) - self.assertEqual(task[0].subject, 'Test Temp Task parent') - self.assertEqual(getdate(task[0].exp_end_date), add_days(nowdate()+ 1 + 1)) - print(task[0].depends_on) + self.assertEqual(tasks[1].subject, 'Test Temp Task child 1') + self.assertEqual(getdate(tasks[1].exp_end_date), add_days(nowdate()+ 1 + 3)) + self.assertEqual(tasks[1].parent_task, tasks[0].name) - self.assertEqual(task[1].subject, 'Test Temp Task child 1') - self.assertEqual(getdate(task[1].exp_end_date), add_days(nowdate()+ 1 + 3)) - self.assertEqual(task[1].parent_task, task[0].name) - - self.assertEqual(task[2].subject, 'Test Temp Task child 2') - self.assertEqual(getdate(task[2].exp_end_date), add_days(nowdate()+ 2 + 3)) - self.assertEqual(task[2].parent_task, task[0].name) + self.assertEqual(tasks[2].subject, 'Test Temp Task child 2') + self.assertEqual(getdate(tasks[2].exp_end_date), add_days(nowdate()+ 2 + 3)) + self.assertEqual(tasks[2].parent_task, tasks[0].name) self.assertEqual(len(tasks), 3) @@ -78,12 +77,12 @@ class TestProject(unittest.TestCase): project = get_project("Test Project with Templ - tasks with parent-child", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - self.assertEqual(task[0].subject, 'Test Temp Task for dependency') - self.assertEqual(getdate(task[0].exp_end_date), add_days(nowdate()+ 3 + 1)) + self.assertEqual(tasks[0].subject, 'Test Temp Task for dependency') + self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate()+ 3 + 1)) - self.assertEqual(task[1].subject, 'Test Temp Task with dependency') - self.assertEqual(getdate(task[1].exp_end_date), add_days(nowdate()+ 2 + 2)) - self.assertEqual(task[1].depends_on, task[0].name) + self.assertEqual(tasks[1].subject, 'Test Temp Task with dependency') + self.assertEqual(getdate(tasks[1].exp_end_date), add_days(nowdate()+ 2 + 2)) + self.assertEqual(tasks[1].depends_on, tasks[0].name) self.assertEqual(len(tasks), 2) diff --git a/erpnext/projects/doctype/project_template/project_template.js b/erpnext/projects/doctype/project_template/project_template.js index acc6849f2b..7668df3e13 100644 --- a/erpnext/projects/doctype/project_template/project_template.js +++ b/erpnext/projects/doctype/project_template/project_template.js @@ -6,12 +6,12 @@ frappe.ui.form.on('Project Template', { // } setup: function (frm) { - me.frm.set_query("task", "tasks", function (doc, cdt, cdn) { + frm.set_query("task", "tasks", function () { return { filters: { "is_template": 1 } - } + }; }); } }); diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index dd98d02c02..379365f999 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -27,15 +27,13 @@ def get_project_template(project_template_name="Test Project Template", project_ def make_project_template(project_template_name, project_tasks=[]): if not frappe.db.exists('Project Template', project_template_name): - doc = frappe.get_doc(dict( + frappe.get_doc(dict( doctype = 'Project Template', name = project_template_name, tasks = project_tasks or [ create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), ] - )) - print("doc",doc.tasks) - doc.insert() + )).insert() return frappe.get_doc('Project Template', project_template_name) \ No newline at end of file From e15ef1e19f124436d8705d633a02388a1107056f Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 10 Dec 2020 20:55:25 +0530 Subject: [PATCH 05/29] fix: corrected tests --- erpnext/projects/doctype/project/project.py | 35 +++++++----- .../projects/doctype/project/test_project.py | 57 ++++++++++++------- .../project_template/test_project_template.py | 19 ++++--- erpnext/projects/doctype/task/test_task.py | 8 +-- 4 files changed, 71 insertions(+), 48 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 04a0fb6c4f..dfb54a2f77 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -54,17 +54,15 @@ class Project(Document): self.project_type = template.project_type # create tasks from template + project_tasks = [] for task in template.tasks: template_task_details = frappe.get_doc("Task", task.task) - project_task = self.create_task_from_template(template_task_details) + project_tasks.append(self.create_task_from_template(template_task_details)) - if template_task_details.depends_on: - for child_task in template_task_details.depends_on: - child_task_details = frappe.get_doc("Task",child_task.task) - self.create_task_from_template(child_task_details, project_task) + #self.dependency_mapping(template.tasks, project_tasks) - def create_task_from_template(self, task_details, project_task=None): - doc = frappe.get_doc(dict( + def create_task_from_template(self, task_details): + return frappe.get_doc(dict( doctype = 'Task', subject = task_details.subject, project = self.name, @@ -75,14 +73,21 @@ class Project(Document): task_weight = task_details.task_weight, type = task_details.type, issue = task_details.issue, - is_group = task_details.is_group - )) - if task_details.parent_task and project_task: - doc.parent_task = project_task.name - if not task_details.is_group: - doc.depends_on = task_details.depends_on - doc.insert() - return doc + is_group = task_details.is_group, + start = task_details.start, + duration = task_details.duration + )).insert() + + """ def dependency_mapping(self, template_tasks, project_tasks): + for tmp_task in template_tasks: + for prj_task in project_tasks: + if tmp_task.subject == prj_task.subject: + if tmp_task.depends_on and not prj_task.depends_on: + for child_task in tmp_task.depends_on: + child_task_detai + prj_task.depends_on = tmp_task.depends_on + """ + def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: return True diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 52f877b8b7..f9bb1b3ac4 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -20,15 +20,16 @@ class TestProject(unittest.TestCase): frappe.db.sql('delete from tabTask where project = "Test Project with Templ - no parent and dependend tasks"') frappe.delete_doc('Project', 'Test Project with Templ - no parent and dependend tasks') - if not frappe.db.exists("Task", "Test Temp Task with no parent and dependency"): - task1 = create_task(subject="Test Temp Task with no parent and dependency", is_template=1, begin=0, duration=3) + task1 = task_exists("Test Temp Task with no parent and dependency") + if not task1: + task1 = create_task(subject="Test Temp Task with no parent and dependency", is_template=1, begin=5, duration=3) template = make_project_template("Test Project Template - no parent and dependend tasks", [task1]) project = get_project("Test Project with Templ - no parent and dependend tasks", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') self.assertEqual(tasks[0].subject, 'Test Temp Task with no parent and dependency') - self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate() + 0 + 3)) + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) self.assertEqual(len(tasks), 1) def test_project_template_having_parent_child_tasks(self): @@ -36,28 +37,31 @@ class TestProject(unittest.TestCase): frappe.db.sql('delete from tabTask where project = "Test Project with Templ - tasks with parent-child"') frappe.delete_doc('Project', 'Test Project with Templ - tasks with parent-child') - if not frappe.db.exists("Task", "Test Temp Task parent"): + task1 = task_exists("Test Temp Task parent") + if not task1: task1 = create_task(subject="Test Temp Task parent", is_group=1, is_template=1, begin=1, duration=1) - if not frappe.db.exists("Task", "Test Temp Task child 1"): + task2 = task_exists("Test Temp Task child 1") + if not task2: task2 = create_task(subject="Test Temp Task child 1", parent_task=task1.name, is_template=1, begin=1, duration=3) - if not frappe.db.exists("Task", "Test Temp Task child 2"): + task3 = task_exists("Test Temp Task child 2") + if not task3: task3 = create_task(subject="Test Temp 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", [task1, task2, task3]) + template = make_project_template("Test Project Template - tasks with parent-child", [task1]) project = get_project("Test Project with Templ - tasks with parent-child", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - print(tasks, type(tasks), len(tasks)) + print(tasks[0].duration) self.assertEqual(tasks[0].subject, 'Test Temp Task parent') - self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate()+ 1 + 1)) + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) self.assertEqual(tasks[1].subject, 'Test Temp Task child 1') - self.assertEqual(getdate(tasks[1].exp_end_date), add_days(nowdate()+ 1 + 3)) + self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, tasks[1])) self.assertEqual(tasks[1].parent_task, tasks[0].name) self.assertEqual(tasks[2].subject, 'Test Temp Task child 2') - self.assertEqual(getdate(tasks[2].exp_end_date), add_days(nowdate()+ 2 + 3)) + self.assertEqual(getdate(tasks[2].exp_end_date), calculate_end_date(project, tasks[2])) self.assertEqual(tasks[2].parent_task, tasks[0].name) self.assertEqual(len(tasks), 3) @@ -67,22 +71,24 @@ class TestProject(unittest.TestCase): frappe.db.sql('delete from tabTask where project = "Test Project with Templ - dependent tasks"') frappe.delete_doc('Project', 'Test Project with Templ - dependent tasks') - if not frappe.db.exists("Task", "Test Temp Task for dependency"): + task1 = task_exists("Test Temp Task for dependency") + if not task1: task1 = create_task(subject="Test Temp Task for dependency", is_template=1, begin=3, duration=1) - if not frappe.db.exists("Task", "Test Temp Task with dependency"): + task2 = task_exists("Test Temp Task with dependency") + if not task2: task2 = create_task(subject="Test Temp Task with dependency", depends_on=task1.name, is_template=1, begin=2, duration=2) - template = make_project_template("Test Project Template - tasks with parent-child", [task1, task2]) + template = make_project_template("Test Project with Templ - dependent tasks", [task2]) project = get_project("Test Project with Templ - tasks with parent-child", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - self.assertEqual(tasks[0].subject, 'Test Temp Task for dependency') - self.assertEqual(getdate(tasks[0].exp_end_date), add_days(nowdate()+ 3 + 1)) + self.assertEqual(tasks[0].subject, 'Test Temp Task with dependency') + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) + self.assertEqual(tasks[0].depends_on, tasks[1].name) - self.assertEqual(tasks[1].subject, 'Test Temp Task with dependency') - self.assertEqual(getdate(tasks[1].exp_end_date), add_days(nowdate()+ 2 + 2)) - self.assertEqual(tasks[1].depends_on, tasks[0].name) + self.assertEqual(tasks[1].subject, 'Test Temp Task for dependency') + self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, tasks[1]) ) self.assertEqual(len(tasks), 2) @@ -93,7 +99,7 @@ def get_project(name, template): project_name = name, status = 'Open', project_template = template.name, - expected_start_date = '2019-01-01' + expected_start_date = nowdate() )).insert() return project @@ -114,4 +120,13 @@ def make_project(args): if not frappe.db.exists("Project", args.project_name): project.insert() - return project \ No newline at end of file + 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, task): + return getdate(add_days(project.expected_start_date, task.start + task.duration)) \ No newline at end of file diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 379365f999..6c6b78368e 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -17,9 +17,7 @@ def get_project_template(project_template_name="Test Project Template", project_ name = project_template_name, tasks = project_tasks or [ create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), - create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2), - create_task(subject="_Test Template Task 3", is_template=1, begin=2, duration=4), - create_task(subject="_Test Template Task 4", is_template=1, begin=3, duration=2), + create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2) ] )).insert() @@ -27,13 +25,18 @@ def get_project_template(project_template_name="Test Project Template", project_ def make_project_template(project_template_name, project_tasks=[]): if not frappe.db.exists('Project Template', project_template_name): - frappe.get_doc(dict( - doctype = 'Project Template', - name = project_template_name, - tasks = project_tasks or [ + project_tasks = project_tasks or [ create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), create_task(subject="_Test Template Task 2", is_template=1, begin=0, 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) \ No newline at end of file diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 181a2dc316..d43d132e80 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -104,11 +104,12 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, pa task.subject = subject task.exp_start_date = start or nowdate() task.exp_end_date = end or nowdate() - task.project = project or "_Test Project" - task.is_template = is_template, + task.project = project + task.is_template = is_template task.start = begin - task.duration = duration, + task.duration = duration task.is_group = is_group + task.parent_task = parent_task if save: task.save() else: @@ -120,5 +121,4 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, pa }) if save: task.save() - return task From f9751f1f95e9f69701da999bcb342c6b620bcf95 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 14 Dec 2020 16:20:02 +0530 Subject: [PATCH 06/29] feat: project template having dependent tasks --- erpnext/projects/doctype/project/project.py | 33 +- .../project_template/project_template.py | 23 +- erpnext/projects/doctype/task/task.py | 451 +++++++++--------- 3 files changed, 282 insertions(+), 225 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index dfb54a2f77..2d3339773a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -55,11 +55,13 @@ class Project(Document): # create tasks from template project_tasks = [] + tmp_task_details = [] for task in template.tasks: template_task_details = frappe.get_doc("Task", task.task) + tmp_task_details.append(template_task_details) project_tasks.append(self.create_task_from_template(template_task_details)) - #self.dependency_mapping(template.tasks, project_tasks) + self.dependency_mapping(tmp_task_details, project_tasks) def create_task_from_template(self, task_details): return frappe.get_doc(dict( @@ -78,16 +80,33 @@ class Project(Document): duration = task_details.duration )).insert() - """ def dependency_mapping(self, template_tasks, project_tasks): + def dependency_mapping(self, template_tasks, project_tasks): for tmp_task in template_tasks: for prj_task in project_tasks: if tmp_task.subject == prj_task.subject: - if tmp_task.depends_on and not prj_task.depends_on: - for child_task in tmp_task.depends_on: - child_task_detai - prj_task.depends_on = tmp_task.depends_on - """ + self.check_depends_on_value(tmp_task, prj_task, project_tasks) + self.check_for_parent_tasks(tmp_task, prj_task, project_tasks) + def check_depends_on_value(self, tmp_task, prj_task, project_tasks): + if tmp_task.depends_on and not prj_task.depends_on: + for child_task in tmp_task.depends_on: + child_task_subject = frappe.db.get_value("Task", child_task.task, "subject") + corresponding_prj_task = list(filter(lambda x: x.subject == child_task_subject, project_tasks)) + if len(corresponding_prj_task): + prj_task.append("depends_on",{ + "task": corresponding_prj_task[0].name + }) + prj_task.save() + + def check_for_parent_tasks(self, tmp_task, prj_task, project_tasks): + if tmp_task.parent_task and not prj_task.parent_task: + parent_task_subject = frappe.db.get_value("Task", tmp_task.parent_task, "subject") + corresponding_prj_task = list(filter(lambda x: x.subject == parent_task_subject, project_tasks)) + if len(corresponding_prj_task): + prj_task.parent_task = corresponding_prj_task[0].name + print(prj_task.name, prj_task.parent_task, corresponding_prj_task[0].name) + prj_task.save() + print(prj_task.name, corresponding_prj_task[0].name) def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: return True diff --git a/erpnext/projects/doctype/project_template/project_template.py b/erpnext/projects/doctype/project_template/project_template.py index ac78135fc4..1beebf7a25 100644 --- a/erpnext/projects/doctype/project_template/project_template.py +++ b/erpnext/projects/doctype/project_template/project_template.py @@ -3,8 +3,27 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document +from frappe import _ 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 = """{0}""".format(task_details.name) + dependency_task_format = """{0}""".format(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 diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index fb84094ffe..072a848f26 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -17,291 +17,310 @@ class CircularReferenceError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass class Task(NestedSet): - nsm_parent_field = 'parent_task' + nsm_parent_field = 'parent_task' - def get_feed(self): - return '{0}: {1}'.format(_(self.status), self.subject) + def get_feed(self): + return '{0}: {1}'.format(_(self.status), self.subject) - def get_customer_details(self): - cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) - if cust: - ret = {'customer_name': cust and cust[0][0] or ''} - return ret + def get_customer_details(self): + cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) + if cust: + ret = {'customer_name': cust and cust[0][0] or ''} + return ret - def validate(self): - self.validate_dates() - self.validate_parent_project_dates() - self.validate_progress() - self.validate_status() - self.update_depends_on() + def validate(self): + self.validate_dates() + self.validate_parent_project_dates() + self.validate_progress() + self.validate_status() + self.update_depends_on() + self.validate_dependencies_for_template_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): - frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ - frappe.bold("Expected End Date"))) + 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): + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ + frappe.bold("Expected End Date"))) - if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): - frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ - frappe.bold("Actual End Date"))) + if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ + frappe.bold("Actual End Date"))) - def validate_parent_project_dates(self): - if not self.project or frappe.flags.in_test: - return + def validate_parent_project_dates(self): + if not self.project or frappe.flags.in_test: + return - expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") + expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") - if expected_end_date: - validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") - validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") + if expected_end_date: + validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") - def validate_status(self): - if self.status!=self.get_db_value("status") and self.status == "Completed": - for d in self.depends_on: - if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): - frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task))) + def validate_status(self): + if self.status!=self.get_db_value("status") and self.status == "Completed": + for d in self.depends_on: + if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): + frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task))) - close_all_assignments(self.doctype, self.name) + close_all_assignments(self.doctype, self.name) - def validate_progress(self): - if flt(self.progress or 0) > 100: - frappe.throw(_("Progress % for a task cannot be more than 100.")) + def validate_progress(self): + if flt(self.progress or 0) > 100: + frappe.throw(_("Progress % for a task cannot be more than 100.")) - if flt(self.progress) == 100: - self.status = 'Completed' + if flt(self.progress) == 100: + self.status = 'Completed' - if self.status == 'Completed': - self.progress = 100 + if self.status == 'Completed': + self.progress = 100 - def update_depends_on(self): - depends_on_tasks = self.depends_on_tasks or "" - for d in self.depends_on: - if d.task and not d.task in depends_on_tasks: - depends_on_tasks += d.task + "," - self.depends_on_tasks = depends_on_tasks + 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 = """{0}""".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 = """{0}""".format(task.task) + frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) - def update_nsm_model(self): - frappe.utils.nestedset.update_nsm(self) + def update_depends_on(self): + depends_on_tasks = self.depends_on_tasks or "" + for d in self.depends_on: + if d.task and not d.task in depends_on_tasks: + depends_on_tasks += d.task + "," + self.depends_on_tasks = depends_on_tasks - def on_update(self): - self.update_nsm_model() - self.check_recursion() - self.reschedule_dependent_tasks() - self.update_project() - self.unassign_todo() - self.populate_depends_on() + def update_nsm_model(self): + frappe.utils.nestedset.update_nsm(self) - def unassign_todo(self): - if self.status == "Completed": - close_all_assignments(self.doctype, self.name) - if self.status == "Cancelled": - clear(self.doctype, self.name) + def on_update(self): + self.update_nsm_model() + self.check_recursion() + self.reschedule_dependent_tasks() + self.update_project() + self.unassign_todo() + self.populate_depends_on() - def update_total_expense_claim(self): - self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` - where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0] + def unassign_todo(self): + if self.status == "Completed": + close_all_assignments(self.doctype, self.name) + if self.status == "Cancelled": + clear(self.doctype, self.name) - 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 `tabTimesheet Detail` 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 - self.total_billing_amount= tl.total_billing_amount - self.actual_time= tl.time - self.act_start_date= tl.start_date - self.act_end_date= tl.end_date + def update_total_expense_claim(self): + self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` + where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0] - def update_project(self): - if self.project and not self.flags.from_project: - frappe.get_cached_doc("Project", self.project).update_project() + 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 `tabTimesheet Detail` 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 + self.total_billing_amount= tl.total_billing_amount + self.actual_time= tl.time + self.act_start_date= tl.start_date + self.act_end_date= tl.end_date - def check_recursion(self): - if self.flags.ignore_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]) + def update_project(self): + if self.project and not self.flags.from_project: + frappe.get_cached_doc("Project", self.project).update_project() - if count == 15: - break + def check_recursion(self): + if self.flags.ignore_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]) - 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` as parent - where parent.project = %(project)s - and parent.name in ( - select parent from `tabTask Depends On` as child - where child.task = %(task)s and child.project = %(project)s) - """, {'project': self.project, 'task':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.ignore_recursion_check = True - task.save() + if count == 15: + break - def has_webform_permission(self): - project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user") - if project_user: - return True + 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` as parent + where parent.project = %(project)s + and parent.name in ( + select parent from `tabTask Depends On` as child + where child.task = %(task)s and child.project = %(project)s) + """, {'project': self.project, 'task':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.ignore_recursion_check = True + task.save() - def populate_depends_on(self): - if self.parent_task: - parent = frappe.get_doc('Task', self.parent_task) - if not self.name in [row.task for row in parent.depends_on]: - parent.append("depends_on", { - "doctype": "Task Depends On", - "task": self.name, - "subject": self.subject - }) - parent.save() + def has_webform_permission(self): + project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user") + if project_user: + return True - def on_trash(self): - if check_if_child_exists(self.name): - throw(_("Child Task exists for this Task. You can not delete this Task.")) + def populate_depends_on(self): + if self.parent_task: + parent = frappe.get_doc('Task', self.parent_task) + if not self.name in [row.task for row in parent.depends_on]: + parent.append("depends_on", { + "doctype": "Task Depends On", + "task": self.name, + "subject": self.subject + }) + parent.save() - self.update_nsm_model() + def on_trash(self): + if check_if_child_exists(self.name): + throw(_("Child Task exists for this Task. You can not delete this Task.")) - def after_delete(self): - self.update_project() + self.update_nsm_model() - def update_status(self): - if self.status not in ('Cancelled', 'Completed') and self.exp_end_date: - from datetime import datetime - if self.exp_end_date < datetime.now().date(): - self.db_set('status', 'Overdue', update_modified=False) - self.update_project() + def after_delete(self): + self.update_project() + + def update_status(self): + if self.status not in ('Cancelled', 'Completed') and self.exp_end_date: + from datetime import datetime + if self.exp_end_date < datetime.now().date(): + self.db_set('status', 'Overdue', update_modified=False) + self.update_project() @frappe.whitelist() def check_if_child_exists(name): - child_tasks = frappe.get_all("Task", filters={"parent_task": name}) - child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks] - return child_tasks + child_tasks = frappe.get_all("Task", filters={"parent_task": name}) + child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks] + return child_tasks @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_project(doctype, txt, searchfield, start, page_len, filters): - from erpnext.controllers.queries import get_match_cond - return frappe.db.sql(""" select name from `tabProject` - where %(key)s like %(txt)s - %(mcond)s - order by name - limit %(start)s, %(page_len)s""" % { - 'key': searchfield, - 'txt': frappe.db.escape('%' + txt + '%'), - 'mcond':get_match_cond(doctype), - 'start': start, - 'page_len': page_len - }) + from erpnext.controllers.queries import get_match_cond + return frappe.db.sql(""" select name from `tabProject` + where %(key)s like %(txt)s + %(mcond)s + order by name + limit %(start)s, %(page_len)s""" % { + 'key': searchfield, + 'txt': frappe.db.escape('%' + txt + '%'), + 'mcond':get_match_cond(doctype), + 'start': start, + 'page_len': page_len + }) @frappe.whitelist() def set_multiple_status(names, status): - names = json.loads(names) - for name in names: - task = frappe.get_doc("Task", name) - task.status = status - task.save() + names = json.loads(names) + for name in names: + task = frappe.get_doc("Task", name) + task.status = status + task.save() def set_tasks_as_overdue(): - tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"]) - for task in tasks: - if task.status == "Pending Review": - if getdate(task.review_date) > getdate(today()): - continue - frappe.get_doc("Task", task.name).update_status() + tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"]) + for task in tasks: + if task.status == "Pending Review": + if getdate(task.review_date) > getdate(today()): + continue + frappe.get_doc("Task", task.name).update_status() @frappe.whitelist() def make_timesheet(source_name, target_doc=None, ignore_permissions=False): - def set_missing_values(source, target): - target.append("time_logs", { - "hours": source.actual_time, - "completed": source.status == "Completed", - "project": source.project, - "task": source.name - }) + def set_missing_values(source, target): + target.append("time_logs", { + "hours": source.actual_time, + "completed": source.status == "Completed", + "project": source.project, + "task": source.name + }) - doclist = get_mapped_doc("Task", source_name, { - "Task": { - "doctype": "Timesheet" - } - }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) + doclist = get_mapped_doc("Task", source_name, { + "Task": { + "doctype": "Timesheet" + } + }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) - return doclist + return doclist @frappe.whitelist() def get_children(doctype, parent, task=None, project=None, is_root=False): - filters = [['docstatus', '<', '2']] + filters = [['docstatus', '<', '2']] - if task: - filters.append(['parent_task', '=', task]) - elif parent and not is_root: - # via expand child - filters.append(['parent_task', '=', parent]) - else: - filters.append(['ifnull(`parent_task`, "")', '=', '']) + if task: + filters.append(['parent_task', '=', task]) + elif parent and not is_root: + # via expand child + filters.append(['parent_task', '=', parent]) + else: + filters.append(['ifnull(`parent_task`, "")', '=', '']) - if project: - filters.append(['project', '=', project]) + if project: + filters.append(['project', '=', project]) - tasks = frappe.get_list(doctype, fields=[ - 'name as value', - 'subject as title', - 'is_group as expandable' - ], filters=filters, order_by='name') + tasks = frappe.get_list(doctype, fields=[ + 'name as value', + 'subject as title', + 'is_group as expandable' + ], filters=filters, order_by='name') - # return tasks - return tasks + # return tasks + return tasks @frappe.whitelist() def add_node(): - from frappe.desk.treeview import make_tree_args - args = frappe.form_dict - args.update({ - "name_field": "subject" - }) - args = make_tree_args(**args) + from frappe.desk.treeview import make_tree_args + args = frappe.form_dict + args.update({ + "name_field": "subject" + }) + args = make_tree_args(**args) - if args.parent_task == 'All Tasks' or args.parent_task == args.project: - args.parent_task = None + if args.parent_task == 'All Tasks' or args.parent_task == args.project: + args.parent_task = None - frappe.get_doc(args).insert() + frappe.get_doc(args).insert() @frappe.whitelist() def add_multiple_tasks(data, parent): - data = json.loads(data) - new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""} - new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or "" + data = json.loads(data) + new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""} + new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or "" - for d in data: - if not d.get("subject"): continue - new_doc['subject'] = d.get("subject") - new_task = frappe.get_doc(new_doc) - new_task.insert() + for d in data: + if not d.get("subject"): continue + new_doc['subject'] = d.get("subject") + new_task = frappe.get_doc(new_doc) + new_task.insert() def on_doctype_update(): - frappe.db.add_index("Task", ["lft", "rgt"]) + frappe.db.add_index("Task", ["lft", "rgt"]) def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date): - if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: - frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) + if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: + frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) - if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: - frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) + if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) From 23f0debf8807eeac894cfb5d628fddb80bf0fd72 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 15 Dec 2020 10:00:21 +0530 Subject: [PATCH 07/29] fix: tests --- erpnext/projects/doctype/project/test_project.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index f9bb1b3ac4..ea54774d52 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -79,16 +79,16 @@ class TestProject(unittest.TestCase): if not task2: task2 = create_task(subject="Test Temp Task with dependency", depends_on=task1.name, is_template=1, begin=2, duration=2) - template = make_project_template("Test Project with Templ - dependent tasks", [task2]) - project = get_project("Test Project with Templ - tasks with parent-child", template) + template = make_project_template("Test Project with Templ - dependent tasks", [task1, task2]) + project = get_project("Test Project with Templ - dependent tasks", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - self.assertEqual(tasks[0].subject, 'Test Temp Task with dependency') - self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) - self.assertEqual(tasks[0].depends_on, tasks[1].name) + self.assertEqual(tasks[1].subject, 'Test Temp Task with dependency') + self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, tasks[1])) + self.assertTrue(tasks[1].depends_on_tasks.find(tasks[0].name) >= 0 ) - self.assertEqual(tasks[1].subject, 'Test Temp Task for dependency') - self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, tasks[1]) ) + self.assertEqual(tasks[0].subject, 'Test Temp Task for dependency') + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0]) ) self.assertEqual(len(tasks), 2) From caf67e608f871adca275e001dddc96c96af4ea77 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 15 Dec 2020 10:00:31 +0530 Subject: [PATCH 08/29] fix: tests --- erpnext/projects/doctype/project/project.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2d3339773a..5a9375a0e6 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -82,31 +82,29 @@ class Project(Document): def dependency_mapping(self, template_tasks, project_tasks): for tmp_task in template_tasks: - for prj_task in project_tasks: - if tmp_task.subject == prj_task.subject: - self.check_depends_on_value(tmp_task, prj_task, project_tasks) - self.check_for_parent_tasks(tmp_task, prj_task, project_tasks) + prj_task = list(filter(lambda x: x.subject == tmp_task.subject, project_tasks))[0] + self.check_depends_on_value(tmp_task, prj_task, project_tasks) + self.check_for_parent_tasks(tmp_task, prj_task, project_tasks) def check_depends_on_value(self, tmp_task, prj_task, project_tasks): - if tmp_task.depends_on and not prj_task.depends_on: - for child_task in tmp_task.depends_on: + if tmp_task.get("depends_on") and not prj_task.get("depends_on"): + for child_task in tmp_task.get("depends_on"): child_task_subject = frappe.db.get_value("Task", child_task.task, "subject") corresponding_prj_task = list(filter(lambda x: x.subject == child_task_subject, project_tasks)) if len(corresponding_prj_task): prj_task.append("depends_on",{ "task": corresponding_prj_task[0].name }) + print(prj_task.name) prj_task.save() def check_for_parent_tasks(self, tmp_task, prj_task, project_tasks): - if tmp_task.parent_task and not prj_task.parent_task: - parent_task_subject = frappe.db.get_value("Task", tmp_task.parent_task, "subject") + if tmp_task.get("parent_task") and not prj_task.get("parent_task"): + parent_task_subject = frappe.db.get_value("Task", tmp_task.get("parent_task"), "subject") corresponding_prj_task = list(filter(lambda x: x.subject == parent_task_subject, project_tasks)) if len(corresponding_prj_task): prj_task.parent_task = corresponding_prj_task[0].name - print(prj_task.name, prj_task.parent_task, corresponding_prj_task[0].name) prj_task.save() - print(prj_task.name, corresponding_prj_task[0].name) def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: return True From a6fef7ae6bbdba8a4f922ebdfbf337033c41ac4d Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 15 Dec 2020 11:50:18 +0530 Subject: [PATCH 09/29] feat: parent-child relation tasks --- erpnext/projects/doctype/project/project.py | 2 +- erpnext/projects/doctype/project/test_project.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 5a9375a0e6..13e72fec8a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -83,6 +83,7 @@ class Project(Document): def dependency_mapping(self, template_tasks, project_tasks): for tmp_task in template_tasks: prj_task = list(filter(lambda x: x.subject == tmp_task.subject, project_tasks))[0] + prj_task = frappe.get_doc("Task", prj_task.name) self.check_depends_on_value(tmp_task, prj_task, project_tasks) self.check_for_parent_tasks(tmp_task, prj_task, project_tasks) @@ -95,7 +96,6 @@ class Project(Document): prj_task.append("depends_on",{ "task": corresponding_prj_task[0].name }) - print(prj_task.name) prj_task.save() def check_for_parent_tasks(self, tmp_task, prj_task, project_tasks): diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index ea54774d52..c3f56b8e86 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -49,10 +49,10 @@ class TestProject(unittest.TestCase): if not task3: task3 = create_task(subject="Test Temp 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", [task1]) + template = make_project_template("Test Project Template - tasks with parent-child", [task1, task2, task3]) project = get_project("Test Project with Templ - tasks with parent-child", template) tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') - print(tasks[0].duration) + self.assertEqual(tasks[0].subject, 'Test Temp Task parent') self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) From 87b477a31126e478c2bcc77861975e015474bc6a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 16 Dec 2020 13:37:21 +0530 Subject: [PATCH 10/29] feat: patch for project template tasks --- erpnext/patches.txt | 1 + .../v13_0/update_project_template_tasks.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 erpnext/patches/v13_0/update_project_template_tasks.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 86ac613ae5..435511210b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -741,3 +741,4 @@ 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.add_po_to_global_search erpnext.patches.v13_0.update_returned_qty_in_pr_dn +erpnext.patches.v13_0.update_project_template_tasks diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py new file mode 100644 index 0000000000..55f0ff4505 --- /dev/null +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -0,0 +1,32 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + templates = frappe.get_list("Project Template", fields = ["name"]) + for template_name in templates: + template = frappe.get_doc("Project Template", template_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.name) + if replace_tasks: + template.tasks = [] + for tsk in new_tasks: + template.append("tasks", { + "task": tsk + }) + template.save() \ No newline at end of file From d44f45c57be854c1c6c625ffccf86b56203c3dd7 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 16 Dec 2020 16:28:09 +0530 Subject: [PATCH 11/29] fix: sider issues --- erpnext/projects/doctype/project/test_project.py | 7 ++++--- erpnext/projects/doctype/task/task.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index c3f56b8e86..ce56a50b4e 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -106,17 +106,18 @@ def get_project(name, template): def make_project(args): args = frappe._dict(args) - if args.project_template_name: - template = make_project_template(args.project_template_name) project = frappe.get_doc(dict( doctype = 'Project', project_name = args.project_name, status = 'Open', - project_template = template.name, 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): project.insert() diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 072a848f26..80b764ba4f 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -94,7 +94,7 @@ class Task(NestedSet): def update_depends_on(self): depends_on_tasks = self.depends_on_tasks or "" 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 + "," self.depends_on_tasks = depends_on_tasks @@ -180,7 +180,7 @@ class Task(NestedSet): def populate_depends_on(self): if 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", { "doctype": "Task Depends On", "task": self.name, From 2528d5ee15a5ce9d5d9634eec016946b1416154d Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 16 Dec 2020 18:29:49 +0530 Subject: [PATCH 12/29] fix: tests --- erpnext/projects/doctype/task/test_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index d43d132e80..aded78b857 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -104,7 +104,7 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, pa task.subject = subject task.exp_start_date = start or nowdate() task.exp_end_date = end or nowdate() - task.project = project + task.project = project or "_Test Project" task.is_template = is_template task.start = begin task.duration = duration From f2bff8e220a26b1ed9a662e010b7e15fe91df73e Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 11:54:59 +0530 Subject: [PATCH 13/29] fix: patch relaod doctype --- erpnext/patches/v13_0/update_project_template_tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 55f0ff4505..df1886f616 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe def execute(): + frappe.reload_doctype("Project Template") templates = frappe.get_list("Project Template", fields = ["name"]) for template_name in templates: - template = frappe.get_doc("Project Template", template_name) + template = frappe.get_doc("Project Template", template_name.name) replace_tasks = False new_tasks = [] for task in template.tasks: From 2dbb1d6bc72b28542eec44878c28f1eed069bcca Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 15:49:52 +0530 Subject: [PATCH 14/29] fix: indentation --- erpnext/patches/v13_0/update_project_template_tasks.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index df1886f616..8dd0181ece 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -5,10 +5,9 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doctype("Project Template") - templates = frappe.get_list("Project Template", fields = ["name"]) - for template_name in templates: + for template_name in frappe.db.sql(""" select name from `tabProject Template` """, as_dict=1): template = frappe.get_doc("Project Template", template_name.name) + print(template.tasks) replace_tasks = False new_tasks = [] for task in template.tasks: From 09f0e9111d6fb79868c58f55ead0f18351c6d216 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 17:20:21 +0530 Subject: [PATCH 15/29] fix: patch --- erpnext/patches/v13_0/update_project_template_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 8dd0181ece..0bcd1d3f3a 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -5,9 +5,9 @@ from __future__ import unicode_literals import frappe def execute(): + frappe.reload_doc("projects", "doctype", "project_template”) for template_name in frappe.db.sql(""" select name from `tabProject Template` """, as_dict=1): template = frappe.get_doc("Project Template", template_name.name) - print(template.tasks) replace_tasks = False new_tasks = [] for task in template.tasks: From 79b71462cbdec8fabbb20f80f7e258bb55a65620 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 18:21:34 +0530 Subject: [PATCH 16/29] fix: patch --- erpnext/patches/v13_0/update_project_template_tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 0bcd1d3f3a..1303efd93f 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("projects", "doctype", "project_template”) + frappe.reload_doc("projects", "doctype", "project_template") 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 From b8e656512e8ab34601149c3f3ca0f9831441545a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 20:22:06 +0530 Subject: [PATCH 17/29] fix: test cleanup --- .../project_template/test_project_template.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/erpnext/projects/doctype/project_template/test_project_template.py b/erpnext/projects/doctype/project_template/test_project_template.py index 6c6b78368e..95663cdcbb 100644 --- a/erpnext/projects/doctype/project_template/test_project_template.py +++ b/erpnext/projects/doctype/project_template/test_project_template.py @@ -10,19 +10,6 @@ from erpnext.projects.doctype.task.test_task import create_task class TestProjectTemplate(unittest.TestCase): pass -def get_project_template(project_template_name="Test Project Template", project_tasks=[]): - if not frappe.db.exists('Project Template', project_template_name): - frappe.get_doc(dict( - doctype = 'Project Template', - name = project_template_name, - tasks = project_tasks or [ - create_task(subject="_Test Template Task 1", is_template=1, begin=0, duration=3), - create_task(subject="_Test Template Task 2", is_template=1, begin=0, duration=2) - ] - )).insert() - - return frappe.get_doc('Project Template', project_template_name) - def make_project_template(project_template_name, project_tasks=[]): if not frappe.db.exists('Project Template', project_template_name): project_tasks = project_tasks or [ From 9466e42e7095f7f4ff32230ab7dace6642455ba9 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 21 Dec 2020 20:52:20 +0530 Subject: [PATCH 18/29] fix: change request modifications --- .../v13_0/update_project_template_tasks.py | 8 +- erpnext/projects/doctype/project/project.py | 51 +++++------ .../projects/doctype/project/test_project.py | 87 ++++++++++--------- .../project_template/project_template.py | 5 +- erpnext/projects/doctype/task/task.json | 4 +- erpnext/projects/doctype/task/test_task.py | 2 +- 6 files changed, 82 insertions(+), 75 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 1303efd93f..26c4259281 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -6,7 +6,13 @@ import frappe def execute(): frappe.reload_doc("projects", "doctype", "project_template") - for template_name in frappe.db.sql(""" select name from `tabProject Template` """, as_dict=1): + 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 = [] diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 13e72fec8a..2cdfb7af44 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -59,8 +59,8 @@ class Project(Document): for task in template.tasks: template_task_details = frappe.get_doc("Task", task.task) tmp_task_details.append(template_task_details) - project_tasks.append(self.create_task_from_template(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): @@ -75,36 +75,33 @@ class Project(Document): task_weight = task_details.task_weight, type = task_details.type, issue = task_details.issue, - is_group = task_details.is_group, - start = task_details.start, - duration = task_details.duration + is_group = task_details.is_group )).insert() def dependency_mapping(self, template_tasks, project_tasks): - for tmp_task in template_tasks: - prj_task = list(filter(lambda x: x.subject == tmp_task.subject, project_tasks))[0] - prj_task = frappe.get_doc("Task", prj_task.name) - self.check_depends_on_value(tmp_task, prj_task, project_tasks) - self.check_for_parent_tasks(tmp_task, prj_task, project_tasks) + for template_task in template_tasks: + project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0] + if template_task.get("depends_on") and not project_task.get("depends_on"): + self.check_depends_on_value(template_task, project_task, project_tasks) + if template_task.get("parent_task") and not project_task.get("parent_task"): + self.check_for_parent_tasks(template_task, project_task, project_tasks) - def check_depends_on_value(self, tmp_task, prj_task, project_tasks): - if tmp_task.get("depends_on") and not prj_task.get("depends_on"): - for child_task in tmp_task.get("depends_on"): - child_task_subject = frappe.db.get_value("Task", child_task.task, "subject") - corresponding_prj_task = list(filter(lambda x: x.subject == child_task_subject, project_tasks)) - if len(corresponding_prj_task): - prj_task.append("depends_on",{ - "task": corresponding_prj_task[0].name - }) - prj_task.save() + def check_depends_on_value(self, template_task, project_task, project_tasks): + 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, tmp_task, prj_task, project_tasks): - if tmp_task.get("parent_task") and not prj_task.get("parent_task"): - parent_task_subject = frappe.db.get_value("Task", tmp_task.get("parent_task"), "subject") - corresponding_prj_task = list(filter(lambda x: x.subject == parent_task_subject, project_tasks)) - if len(corresponding_prj_task): - prj_task.parent_task = corresponding_prj_task[0].name - prj_task.save() + def check_for_parent_tasks(self, template_task, project_task, project_tasks): + 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): if self.get("__islocal") or not existing_task_data: return True diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index ce56a50b4e..1d2980ce46 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -17,78 +17,79 @@ class TestProject(unittest.TestCase): """ Test Action: Basic Test of a Project created from template. The template has a single task. """ - frappe.db.sql('delete from tabTask where project = "Test Project with Templ - no parent and dependend tasks"') - frappe.delete_doc('Project', 'Test Project with Templ - no parent and dependend tasks') + project_name = "Test Project with Template - No Parent and Dependend Tasks" + frappe.db.sql(""" delete from tabTask where project = %s """, project_name) + frappe.delete_doc('Project', project_name) - task1 = task_exists("Test Temp Task with no parent and dependency") + task1 = task_exists("Test Template Task with No Parent and Dependency") if not task1: - task1 = create_task(subject="Test Temp Task with no parent and dependency", is_template=1, begin=5, duration=3) + task1 = create_task(subject="Test Template Task with No Parent and Dependency", is_template=1, begin=5, duration=3) - template = make_project_template("Test Project Template - no parent and dependend tasks", [task1]) - project = get_project("Test Project with Templ - no parent and dependend tasks", template) - 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') - self.assertEqual(tasks[0].subject, 'Test Temp Task with no parent and dependency') - self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) + self.assertEqual(tasks[0].subject, 'Test Template Task with No Parent and Dependency') + self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 5, 3)) self.assertEqual(len(tasks), 1) def test_project_template_having_parent_child_tasks(self): + project_name = "Test Project with Template - Tasks with Parent-Child Relation" + frappe.db.sql(""" delete from tabTask where project = %s """, project_name) + frappe.delete_doc('Project', project_name) - frappe.db.sql('delete from tabTask where project = "Test Project with Templ - tasks with parent-child"') - frappe.delete_doc('Project', 'Test Project with Templ - tasks with parent-child') - - task1 = task_exists("Test Temp Task parent") + task1 = task_exists("Test Template Task Parent") if not task1: - task1 = create_task(subject="Test Temp Task parent", is_group=1, is_template=1, begin=1, duration=1) + task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=1) - task2 = task_exists("Test Temp Task child 1") + task2 = task_exists("Test Template Task Child 1") if not task2: - task2 = create_task(subject="Test Temp Task child 1", parent_task=task1.name, is_template=1, begin=1, duration=3) + task2 = create_task(subject="Test Template Task Child 1", parent_task=task1.name, is_template=1, begin=1, duration=3) - task3 = task_exists("Test Temp Task child 2") + task3 = task_exists("Test Template Task Child 2") if not task3: - task3 = create_task(subject="Test Temp Task child 2", parent_task=task1.name, is_template=1, begin=2, duration=3) + 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", [task1, task2, task3]) - project = get_project("Test Project with Templ - tasks with parent-child", template) - tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') + 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'], dict(project=project.name), order_by='creation asc') - self.assertEqual(tasks[0].subject, 'Test Temp Task parent') - self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[0])) + 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 Temp Task child 1') - self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, tasks[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 Temp Task child 2') - self.assertEqual(getdate(tasks[2].exp_end_date), calculate_end_date(project, tasks[2])) + 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) - frappe.db.sql('delete from tabTask where project = "Test Project with Templ - dependent tasks"') - frappe.delete_doc('Project', 'Test Project with Templ - dependent tasks') - - task1 = task_exists("Test Temp Task for dependency") + task1 = task_exists("Test Template Task for Dependency") if not task1: - task1 = create_task(subject="Test Temp Task for dependency", is_template=1, begin=3, duration=1) + task1 = create_task(subject="Test Template Task for Dependency", is_template=1, begin=3, duration=1) - task2 = task_exists("Test Temp Task with dependency") + task2 = task_exists("Test Template Task with Dependency") if not task2: - task2 = create_task(subject="Test Temp Task with dependency", depends_on=task1.name, is_template=1, begin=2, duration=2) + 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 Templ - dependent tasks", [task1, task2]) - project = get_project("Test Project with Templ - dependent tasks", template) - tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc') + 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 Temp Task with dependency') - self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, tasks[1])) + 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 Temp Task for dependency') - self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, tasks[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) @@ -129,5 +130,5 @@ def task_exists(subject): return False return frappe.get_doc("Task", result[0].name) -def calculate_end_date(project, task): - return getdate(add_days(project.expected_start_date, task.start + task.duration)) \ No newline at end of file +def calculate_end_date(project, start, duration): + return getdate(add_days(project.expected_start_date, start + duration)) \ No newline at end of file diff --git a/erpnext/projects/doctype/project_template/project_template.py b/erpnext/projects/doctype/project_template/project_template.py index 1beebf7a25..aace40240c 100644 --- a/erpnext/projects/doctype/project_template/project_template.py +++ b/erpnext/projects/doctype/project_template/project_template.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ +from frappe.utils import get_link_to_form class ProjectTemplate(Document): @@ -18,8 +19,8 @@ class ProjectTemplate(Document): 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 = """{0}""".format(task_details.name) - dependency_task_format = """{0}""".format(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): diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index a9e3d9bc0f..bb55256f7d 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -371,11 +371,13 @@ "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)" @@ -386,7 +388,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2020-12-07 13:26:53.614689", + "modified": "2020-12-21 11:59:24.196834", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index aded78b857..25714f8cde 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -104,7 +104,7 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None, pa task.subject = subject task.exp_start_date = start 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 From 3a26f26671e19df3acd7ef690300810e1d5026d3 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 22 Dec 2020 11:56:59 +0530 Subject: [PATCH 19/29] fix: get_doc to avoid modified error --- erpnext/projects/doctype/project/project.py | 35 ++++++++++--------- .../projects/doctype/project/test_project.py | 2 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2cdfb7af44..97134602f8 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -81,27 +81,28 @@ class Project(Document): 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] - if template_task.get("depends_on") and not project_task.get("depends_on"): - self.check_depends_on_value(template_task, project_task, project_tasks) - if template_task.get("parent_task") and not project_task.get("parent_task"): - self.check_for_parent_tasks(template_task, project_task, project_tasks) + 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): - 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() + 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): - 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() + 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): if self.get("__islocal") or not existing_task_data: return True diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 1d2980ce46..d77b14ce33 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -52,7 +52,7 @@ class TestProject(unittest.TestCase): 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'], dict(project=project.name), order_by='creation asc') + 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)) From 4ebee5014eebcf49669ccabda45c971f3822c814 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 22 Dec 2020 18:14:46 +0530 Subject: [PATCH 20/29] feat: aholiday check before setting start and end date in task --- erpnext/projects/doctype/project/project.py | 20 +++++++++++++++++-- .../projects/doctype/project/test_project.py | 3 --- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 97134602f8..f6bb6e9e74 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -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.holiday_list.holiday_list import is_holiday from frappe.model.document import Document +from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list class Project(Document): def get_feed(self): @@ -69,8 +70,8 @@ class Project(Document): subject = task_details.subject, project = self.name, status = 'Open', - exp_start_date = add_days(self.expected_start_date, task_details.start), - exp_end_date = add_days(self.expected_start_date, task_details.start + task_details.duration), + exp_start_date = self.calculate_start_date(task_details), + exp_end_date = self.calculate_end_date(task_details), description = task_details.description, task_weight = task_details.task_weight, type = task_details.type, @@ -78,6 +79,21 @@ class Project(Document): is_group = task_details.is_group )).insert() + def calculate_start_date(self, task_details): + self.start_date = add_days(self.expected_start_date, task_details.start) + self.start_date = self.update_if_holiday(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 self.update_if_holiday(self.end_date) + + def update_if_holiday(self, date): + holiday_list = self.holiday_list or get_holiday_list() + while is_holiday(holiday_list, date): + date = add_days(date, 1) + return 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] diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index d77b14ce33..0faf97670d 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -14,9 +14,6 @@ from frappe.utils import getdate, nowdate, add_days class TestProject(unittest.TestCase): def test_project_with_template_having_no_parent_and_depend_tasks(self): - """ - Test Action: Basic Test of a Project created from template. The template has a single task. - """ project_name = "Test Project with Template - No Parent and Dependend Tasks" frappe.db.sql(""" delete from tabTask where project = %s """, project_name) frappe.delete_doc('Project', project_name) From 6cf018c762ee4d67bfc83b9f6fc3814b51462734 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 22 Dec 2020 19:40:41 +0530 Subject: [PATCH 21/29] fix: holiday update in tests --- erpnext/projects/doctype/project/project.py | 16 ++++++++-------- erpnext/projects/doctype/project/test_project.py | 8 ++++++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index f6bb6e9e74..60f85b0e7a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -81,18 +81,12 @@ class Project(Document): def calculate_start_date(self, task_details): self.start_date = add_days(self.expected_start_date, task_details.start) - self.start_date = self.update_if_holiday(self.start_date) + 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 self.update_if_holiday(self.end_date) - - def update_if_holiday(self, date): - holiday_list = self.holiday_list or get_holiday_list() - while is_holiday(holiday_list, date): - date = add_days(date, 1) - return date + return update_if_holiday(self.holiday_list, self.end_date) def dependency_mapping(self, template_tasks, project_tasks): for template_task in template_tasks: @@ -547,3 +541,9 @@ def set_project_status(project, status): project.status = status 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 diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 0faf97670d..af978e85fd 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -8,7 +8,7 @@ test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] 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 set_project_status, update_if_holiday from erpnext.projects.doctype.task.test_task import create_task from frappe.utils import getdate, nowdate, add_days @@ -128,4 +128,8 @@ def task_exists(subject): return frappe.get_doc("Task", result[0].name) def calculate_end_date(project, start, duration): - return getdate(add_days(project.expected_start_date, start + duration)) \ No newline at end of file + 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) \ No newline at end of file From 8dec1c142f96bb171c0e23c92ef9d3b1100cf6b6 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 22 Dec 2020 19:55:31 +0530 Subject: [PATCH 22/29] fix: removed unused imports --- erpnext/projects/doctype/project/test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index af978e85fd..97b67b38eb 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -8,7 +8,7 @@ test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] from erpnext.projects.doctype.project_template.test_project_template import make_project_template -from erpnext.projects.doctype.project.project import set_project_status, update_if_holiday +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, nowdate, add_days From 71f203dbc565d170c993668f6f0f92de2303194a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 28 Dec 2020 12:35:19 +0530 Subject: [PATCH 23/29] fix: template task status, subject in project template task --- .../patches/v13_0/update_project_template_tasks.py | 2 ++ .../doctype/project_template/project_template.js | 10 ++++++++++ .../project_template_task/project_template_task.json | 11 +++++++++-- erpnext/projects/doctype/task/task.json | 4 ++-- erpnext/projects/doctype/task/task.py | 2 ++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 26c4259281..f24a2c62f1 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -5,6 +5,8 @@ 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") for template_name in frappe.db.sql(""" select diff --git a/erpnext/projects/doctype/project_template/project_template.js b/erpnext/projects/doctype/project_template/project_template.js index 7668df3e13..04153dc570 100644 --- a/erpnext/projects/doctype/project_template/project_template.js +++ b/erpnext/projects/doctype/project_template/project_template.js @@ -15,3 +15,13 @@ frappe.ui.form.on('Project Template', { }); } }); + +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"); + }); + } +}) diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.json b/erpnext/projects/doctype/project_template_task/project_template_task.json index 80c510db1b..7a552945bd 100644 --- a/erpnext/projects/doctype/project_template_task/project_template_task.json +++ b/erpnext/projects/doctype/project_template_task/project_template_task.json @@ -5,7 +5,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "task" + "task", + "subject" ], "fields": [ { @@ -15,11 +16,17 @@ "label": "Task", "options": "Task", "reqd": 1 + }, + { + "fieldname": "subject", + "fieldtype": "Read Only", + "in_list_view": 1, + "label": "Subject" } ], "istable": 1, "links": [], - "modified": "2020-12-07 13:28:40.961810", + "modified": "2020-12-28 12:10:26.321913", "modified_by": "Administrator", "module": "Projects", "name": "Project Template Task", diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index bb55256f7d..160cc5812f 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -115,7 +115,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled" + "options": "Open\nWorking\nPending Review\nOverdue\nTemplate\nCompleted\nCancelled" }, { "fieldname": "priority", @@ -388,7 +388,7 @@ "is_tree": 1, "links": [], "max_attachments": 5, - "modified": "2020-12-21 11:59:24.196834", + "modified": "2020-12-28 11:32:58.714991", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 80b764ba4f..a2095c95d5 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -56,6 +56,8 @@ class Task(NestedSet): validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") 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": for d in self.depends_on: if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): From 88471854d53745a2756e947ac92c411b782d8a27 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 28 Dec 2020 15:40:23 +0530 Subject: [PATCH 24/29] fix: sider --- .../projects/doctype/project_template/project_template.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/doctype/project_template/project_template.js b/erpnext/projects/doctype/project_template/project_template.js index 04153dc570..3d3c15c6e0 100644 --- a/erpnext/projects/doctype/project_template/project_template.js +++ b/erpnext/projects/doctype/project_template/project_template.js @@ -20,8 +20,8 @@ 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"); - }); + row.subject = value.subject; + refresh_field("tasks"); + }); } -}) +}); From 27fd9e4d7def5cf817773b1f4ac6168d0537767d Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 4 Jan 2021 18:18:00 +0530 Subject: [PATCH 25/29] fix: added empty value in Quality Inspection Reading status --- .../quality_inspection_reading.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json index c1976dd1fb..9baa702754 100644 --- a/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json +++ b/erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json @@ -130,7 +130,7 @@ "label": "Status", "oldfieldname": "status", "oldfieldtype": "Select", - "options": "Accepted\nRejected" + "options": "\nAccepted\nRejected" }, { "fieldname": "section_break_3", @@ -158,7 +158,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-11-16 16:34:29.947856", + "modified": "2021-01-04 18:16:53.978410", "modified_by": "Administrator", "module": "Stock", "name": "Quality Inspection Reading", From 5eef19723d9bc139f2ef1cad03c17f6c42b17b2f Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Tue, 5 Jan 2021 18:47:11 +0100 Subject: [PATCH 26/29] Update item_tax_template_dashboard.py added missing backlink. In Item Groups I can set Item Tax Temple in Table taxes. --- .../doctype/item_tax_template/item_tax_template_dashboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py index acc308e0e6..3d80a9785f 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py @@ -20,7 +20,8 @@ def get_data(): 'items': ['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'] }, { - 'items': ['Item'] + 'label': _('Stock'), + 'items': ['Item Groups', 'Item'] } ] } From fd5ebe9d4a757c5ded79b2cc2e698695e21f2ce8 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 7 Jan 2021 15:16:15 +0530 Subject: [PATCH 27/29] fix: patch and columns --- erpnext/patches/v13_0/update_project_template_tasks.py | 9 +++++++-- .../project_template_task/project_template_task.json | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index f24a2c62f1..886616e0ca 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -8,6 +8,8 @@ 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 @@ -30,11 +32,14 @@ def execute(): description = task.description, is_template = 1 )).insert() - new_tasks.append(new_task.name) + print(new_task) + new_tasks.append(new_task) if replace_tasks: template.tasks = [] for tsk in new_tasks: + print(tsk.name, tsk.subject) template.append("tasks", { - "task": tsk + "task": tsk.name, + "subject": tsk.subject }) template.save() \ No newline at end of file diff --git a/erpnext/projects/doctype/project_template_task/project_template_task.json b/erpnext/projects/doctype/project_template_task/project_template_task.json index 7a552945bd..69530b15b4 100644 --- a/erpnext/projects/doctype/project_template_task/project_template_task.json +++ b/erpnext/projects/doctype/project_template_task/project_template_task.json @@ -10,6 +10,7 @@ ], "fields": [ { + "columns": 2, "fieldname": "task", "fieldtype": "Link", "in_list_view": 1, @@ -18,6 +19,7 @@ "reqd": 1 }, { + "columns": 6, "fieldname": "subject", "fieldtype": "Read Only", "in_list_view": 1, @@ -26,7 +28,7 @@ ], "istable": 1, "links": [], - "modified": "2020-12-28 12:10:26.321913", + "modified": "2021-01-07 15:13:40.995071", "modified_by": "Administrator", "module": "Projects", "name": "Project Template Task", From 2460e101a70f7eb1ef91a30984b2f32f0b78ceff Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 11 Jan 2021 10:05:54 +0530 Subject: [PATCH 28/29] fix: indicator ofor template task --- erpnext/patches/v13_0/update_project_template_tasks.py | 3 +-- erpnext/projects/doctype/task/task_list.js | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 886616e0ca..5fa062306c 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -32,12 +32,11 @@ def execute(): description = task.description, is_template = 1 )).insert() - print(new_task) new_tasks.append(new_task) + if replace_tasks: template.tasks = [] for tsk in new_tasks: - print(tsk.name, tsk.subject) template.append("tasks", { "task": tsk.name, "subject": tsk.subject diff --git a/erpnext/projects/doctype/task/task_list.js b/erpnext/projects/doctype/task/task_list.js index 941fe97546..39734ee8b1 100644 --- a/erpnext/projects/doctype/task/task_list.js +++ b/erpnext/projects/doctype/task/task_list.js @@ -20,7 +20,8 @@ frappe.listview_settings['Task'] = { "Pending Review": "orange", "Working": "orange", "Completed": "green", - "Cancelled": "dark grey" + "Cancelled": "dark grey", + "Template": "blue" } return [__(doc.status), colors[doc.status], "status,=," + doc.status]; }, From 36fa0512d227693cdc0a904f92d41a93554349c5 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 12 Jan 2021 17:02:19 +0530 Subject: [PATCH 29/29] fix: not able to create dunning from sales invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 50eb400775..566734e7d1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -179,7 +179,7 @@ class SalesInvoice(SellingController): # this sequence because outstanding may get -ve self.make_gl_entries() - + if self.update_stock == 1: self.repost_future_sle_and_gle() @@ -261,10 +261,10 @@ class SalesInvoice(SellingController): self.update_stock_ledger() self.make_gl_entries_on_cancel() - + if self.update_stock == 1: self.repost_future_sle_and_gle() - + frappe.db.set(self, 'status', 'Cancelled') if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') == "Each Transaction": @@ -551,7 +551,7 @@ class SalesInvoice(SellingController): def add_remarks(self): if not self.remarks: if self.po_no and self.po_date: - self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no, + self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no, formatdate(self.po_date)) else: self.remarks = _("No Remarks") @@ -1699,6 +1699,7 @@ def get_mode_of_payment_info(mode_of_payment, company): where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""", (company, mode_of_payment), as_dict=1) +@frappe.whitelist() def create_dunning(source_name, target_doc=None): from frappe.model.mapper import get_mapped_doc from erpnext.accounts.doctype.dunning.dunning import get_dunning_letter_text, calculate_interest_and_amount