Merge pull request #3152 from neilLasrado/project
Relative Dates for Tasks - WIP
This commit is contained in:
commit
13fce8fc3a
0
erpnext/projects/doctype/dependent_task/__init__.py
Normal file
0
erpnext/projects/doctype/dependent_task/__init__.py
Normal file
50
erpnext/projects/doctype/dependent_task/dependent_task.json
Normal file
50
erpnext/projects/doctype/dependent_task/dependent_task.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
|
"creation": "2015-04-29 04:52:48.868079",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"fieldname": "task",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Task",
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Task",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
|
"in_create": 0,
|
||||||
|
"in_dialog": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"istable": 1,
|
||||||
|
"modified": "2015-04-29 04:54:36.024844",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Projects",
|
||||||
|
"name": "Dependent Task",
|
||||||
|
"name_case": "",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_onload": 0,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
10
erpnext/projects/doctype/dependent_task/dependent_task.py
Normal file
10
erpnext/projects/doctype/dependent_task/dependent_task.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class DependentTask(Document):
|
||||||
|
pass
|
@ -40,6 +40,7 @@ erpnext.projects.Task = frappe.ui.form.Controller.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cur_frm.add_fetch('task', 'subject', 'subject');
|
||||||
|
|
||||||
cur_frm.cscript = new erpnext.projects.Task({frm: cur_frm});
|
cur_frm.cscript = new erpnext.projects.Task({frm: cur_frm});
|
||||||
|
|
||||||
|
@ -78,12 +78,26 @@
|
|||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "time_and_budget",
|
"fieldname": "section_break",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "",
|
"label": "Depends On",
|
||||||
"oldfieldtype": "Section Break",
|
"oldfieldtype": "Section Break",
|
||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "depends_on",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "depends_on",
|
||||||
|
"options": "Task Depends On",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_10",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "exp_start_date",
|
"fieldname": "exp_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
@ -249,7 +263,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2015-04-14 07:56:24.481667",
|
"modified": "2015-04-30 05:48:55.176993",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Task",
|
"name": "Task",
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, json
|
import frappe, json
|
||||||
|
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate, date_diff, add_days, cstr
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class CircularReferenceError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class Task(Document):
|
class Task(Document):
|
||||||
def get_feed(self):
|
def get_feed(self):
|
||||||
return '{0}: {1}'.format(_(self.status), self.subject)
|
return '{0}: {1}'.format(_(self.status), self.subject)
|
||||||
@ -36,6 +37,8 @@ class Task(Document):
|
|||||||
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
|
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
|
self.check_recursion()
|
||||||
|
self.reschedule_dependent_tasks()
|
||||||
self.update_percentage()
|
self.update_percentage()
|
||||||
self.update_project()
|
self.update_project()
|
||||||
|
|
||||||
@ -52,8 +55,8 @@ class Task(Document):
|
|||||||
def update_time_and_costing(self):
|
def update_time_and_costing(self):
|
||||||
tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date,
|
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(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount,
|
||||||
sum(hours) as time from `tabTime Log` where project = %s and task = %s and docstatus=1""",
|
sum(hours) as time from `tabTime Log` where task = %s and docstatus=1"""
|
||||||
(self.project, self.name),as_dict=1)[0]
|
,self.name, as_dict=1)[0]
|
||||||
if self.status == "Open":
|
if self.status == "Open":
|
||||||
self.status = "Working"
|
self.status = "Working"
|
||||||
self.total_costing_amount= tl.total_costing_amount
|
self.total_costing_amount= tl.total_costing_amount
|
||||||
@ -68,6 +71,36 @@ class Task(Document):
|
|||||||
project.flags.dont_sync_tasks = True
|
project.flags.dont_sync_tasks = True
|
||||||
project.update_costing()
|
project.update_costing()
|
||||||
project.save()
|
project.save()
|
||||||
|
|
||||||
|
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])
|
||||||
|
if count == 15:
|
||||||
|
break
|
||||||
|
|
||||||
|
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 %s in \
|
||||||
|
(select task from `tabTask Depends On` as child where parent.name = child.parent )", self.name, as_dict=1):
|
||||||
|
task = frappe.get_doc("Task", task_name.name)
|
||||||
|
if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open" :
|
||||||
|
task_duration = date_diff(task.exp_end_date, task.exp_start_date)
|
||||||
|
task.exp_start_date = add_days(end_date, 1)
|
||||||
|
task.exp_end_date = add_days(task.exp_start_date, task_duration)
|
||||||
|
task.flags.ignore_recursion_check = True
|
||||||
|
task.save()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_events(start, end, filters=None):
|
def get_events(start, end, filters=None):
|
||||||
|
@ -1,7 +1,148 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
test_records = frappe.get_test_records('Task')
|
import unittest
|
||||||
|
from frappe.utils import getdate
|
||||||
|
|
||||||
|
# test_records = frappe.get_test_records('Task')
|
||||||
|
|
||||||
|
from erpnext.projects.doctype.task.task import CircularReferenceError
|
||||||
|
|
||||||
|
class TestTask(unittest.TestCase):
|
||||||
|
def test_circular_reference(self):
|
||||||
|
|
||||||
|
task1 = frappe.new_doc('Task')
|
||||||
|
task1.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 1",
|
||||||
|
"exp_start_date": "2015-1-1",
|
||||||
|
"exp_end_date": "2015-1-10"
|
||||||
|
})
|
||||||
|
task1.save()
|
||||||
|
|
||||||
|
task2 = frappe.new_doc('Task')
|
||||||
|
task2.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 2",
|
||||||
|
"exp_start_date": "2015-1-11",
|
||||||
|
"exp_end_date": "2015-1-15",
|
||||||
|
"depends_on":[
|
||||||
|
{
|
||||||
|
"task": task1.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
task2.save()
|
||||||
|
|
||||||
|
task3 = frappe.new_doc('Task')
|
||||||
|
task3.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 2",
|
||||||
|
"exp_start_date": "2015-1-11",
|
||||||
|
"exp_end_date": "2015-1-15",
|
||||||
|
"depends_on":[
|
||||||
|
{
|
||||||
|
"task": task2.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
task3.save()
|
||||||
|
|
||||||
|
task1.append("depends_on", {
|
||||||
|
"task": task3.name
|
||||||
|
})
|
||||||
|
self.assertRaises(CircularReferenceError, task1.save)
|
||||||
|
|
||||||
|
task1.set("depends_on", [])
|
||||||
|
task1.save()
|
||||||
|
|
||||||
|
task4 = frappe.new_doc('Task')
|
||||||
|
task4.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 1",
|
||||||
|
"exp_start_date": "2015-1-1",
|
||||||
|
"exp_end_date": "2015-1-15",
|
||||||
|
"depends_on":[
|
||||||
|
{
|
||||||
|
"task": task1.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
task4.save()
|
||||||
|
|
||||||
|
task3.append("depends_on", {
|
||||||
|
"task": task4.name
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_reschedule_dependent_task(self):
|
||||||
|
task1 = frappe.new_doc('Task')
|
||||||
|
task1.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 1",
|
||||||
|
"exp_start_date": "2015-1-1",
|
||||||
|
"exp_end_date": "2015-1-10"
|
||||||
|
})
|
||||||
|
task1.save()
|
||||||
|
|
||||||
|
task2 = frappe.new_doc('Task')
|
||||||
|
task2.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 2",
|
||||||
|
"exp_start_date": "2015-1-11",
|
||||||
|
"exp_end_date": "2015-1-15",
|
||||||
|
"depends_on":[
|
||||||
|
{
|
||||||
|
"task": task1.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
task2.save()
|
||||||
|
|
||||||
|
task3 = frappe.new_doc('Task')
|
||||||
|
task3.update({
|
||||||
|
"status": "Open",
|
||||||
|
"subject": "_Test Task 3",
|
||||||
|
"exp_start_date": "2015-1-16",
|
||||||
|
"exp_end_date": "2015-1-18",
|
||||||
|
"depends_on":[
|
||||||
|
{
|
||||||
|
"task": task2.name
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
task3.save()
|
||||||
|
|
||||||
|
task1.update({
|
||||||
|
"exp_end_date": "2015-1-20"
|
||||||
|
})
|
||||||
|
task1.save()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
|
||||||
|
|
||||||
|
time_log = frappe.new_doc('Time Log')
|
||||||
|
time_log.update({
|
||||||
|
"from_time": "2015-1-1",
|
||||||
|
"to_time": "2015-1-20",
|
||||||
|
"task": task1.name
|
||||||
|
})
|
||||||
|
time_log.submit()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
|
||||||
|
|
||||||
|
time_log.cancel()
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
|
||||||
|
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
|
||||||
|
self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
|
||||||
|
|
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
|
"creation": "2015-04-29 04:52:48.868079",
|
||||||
|
"custom": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"fieldname": "task",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Task",
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "Task",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "subject",
|
||||||
|
"fieldtype": "Text",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Subject",
|
||||||
|
"options": "",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
|
"in_create": 0,
|
||||||
|
"in_dialog": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
|
"issingle": 0,
|
||||||
|
"istable": 1,
|
||||||
|
"modified": "2015-04-30 05:52:16.250948",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Projects",
|
||||||
|
"name": "Task Depends On",
|
||||||
|
"name_case": "",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_onload": 0,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
10
erpnext/projects/doctype/task_depends_on/task_depends_on.py
Normal file
10
erpnext/projects/doctype/task_depends_on/task_depends_on.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class TaskDependsOn(Document):
|
||||||
|
pass
|
@ -1,6 +1,5 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
Loading…
x
Reference in New Issue
Block a user