BREAKING CHANGE: Remove anti-pattern "Project Task" (#18059)
* BREAKING CHANGE: Remove anti-pattern "Project Task" * fix(tests): remove `tasks` from project/test_records.json * fix(tests) * fix(test): test_employee_onboarding.py * fix(tests): test_expense_claim.py * fix(refactor): cleanup project.py validate/update * fix(refactor): cleanup project.py validate/update * fix(test): test_expense_claim * fix(test): test_expense_claim * fix(test): test_expense_claim, try Test Company 4 * Update project.py
This commit is contained in:
parent
bef897602d
commit
8309fcfbbc
@ -12,7 +12,7 @@ from erpnext.hr.doctype.employee_onboarding.employee_onboarding import Incomplet
|
||||
class TestEmployeeOnboarding(unittest.TestCase):
|
||||
def test_employee_onboarding_incomplete_task(self):
|
||||
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
|
||||
return frappe.get_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
|
||||
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
|
||||
_set_up()
|
||||
applicant = get_job_applicant()
|
||||
onboarding = frappe.new_doc('Employee Onboarding')
|
||||
@ -39,9 +39,10 @@ class TestEmployeeOnboarding(unittest.TestCase):
|
||||
|
||||
# complete the task
|
||||
project = frappe.get_doc('Project', onboarding.project)
|
||||
project.load_tasks()
|
||||
project.tasks[0].status = 'Completed'
|
||||
project.save()
|
||||
for task in frappe.get_all('Task', dict(project=project.name)):
|
||||
task = frappe.get_doc('Task', task.name)
|
||||
task.status = 'Completed'
|
||||
task.save()
|
||||
|
||||
# make employee
|
||||
onboarding.reload()
|
||||
@ -71,4 +72,3 @@ def _set_up():
|
||||
project = "Employee Onboarding : Test Researcher - test@researcher.com"
|
||||
frappe.db.sql("delete from tabProject where name=%s", project)
|
||||
frappe.db.sql("delete from tabTask where project=%s", project)
|
||||
frappe.db.sql("delete from `tabProject Task` where parent=%s", project)
|
||||
|
@ -10,33 +10,36 @@ from erpnext.accounts.doctype.account.test_account import create_account
|
||||
|
||||
test_records = frappe.get_test_records('Expense Claim')
|
||||
test_dependencies = ['Employee']
|
||||
company_name = '_Test Company 4'
|
||||
|
||||
|
||||
class TestExpenseClaim(unittest.TestCase):
|
||||
def test_total_expense_claim_for_project(self):
|
||||
frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """)
|
||||
frappe.db.sql("""delete from `tabProject Task` where parent = "_Test Project 1" """)
|
||||
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
|
||||
frappe.db.sql("delete from `tabExpense Claim` where project='_Test Project 1'")
|
||||
frappe.db.sql("update `tabExpense Claim` set project = '', task = ''")
|
||||
|
||||
frappe.get_doc({
|
||||
"project_name": "_Test Project 1",
|
||||
"doctype": "Project",
|
||||
"doctype": "Project"
|
||||
}).save()
|
||||
|
||||
task = frappe.get_doc({
|
||||
"doctype": "Task",
|
||||
"subject": "_Test Project Task 1",
|
||||
"project": "_Test Project 1"
|
||||
}).save()
|
||||
task = frappe.get_doc(dict(
|
||||
doctype = 'Task',
|
||||
subject = '_Test Project Task 1',
|
||||
status = 'Open',
|
||||
project = '_Test Project 1'
|
||||
)).insert()
|
||||
|
||||
task_name = frappe.db.get_value("Task", {"project": "_Test Project 1"})
|
||||
payable_account = get_payable_account("Wind Power LLC")
|
||||
make_expense_claim(payable_account, 300, 200, "Wind Power LLC","Travel Expenses - WP", "_Test Project 1", task_name)
|
||||
task_name = task.name
|
||||
payable_account = get_payable_account(company_name)
|
||||
|
||||
make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", "_Test Project 1", task_name)
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
|
||||
|
||||
expense_claim2 = make_expense_claim(payable_account, 600, 500, "Wind Power LLC", "Travel Expenses - WP","_Test Project 1", task_name)
|
||||
expense_claim2 = make_expense_claim(payable_account, 600, 500, company_name, "Travel Expenses - _TC4","_Test Project 1", task_name)
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 700)
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
|
||||
@ -48,8 +51,8 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)
|
||||
|
||||
def test_expense_claim_status(self):
|
||||
payable_account = get_payable_account("Wind Power LLC")
|
||||
expense_claim = make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP")
|
||||
payable_account = get_payable_account(company_name)
|
||||
expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4")
|
||||
|
||||
je_dict = make_bank_entry("Expense Claim", expense_claim.name)
|
||||
je = frappe.get_doc(je_dict)
|
||||
@ -66,9 +69,9 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
self.assertEqual(expense_claim.status, "Unpaid")
|
||||
|
||||
def test_expense_claim_gl_entry(self):
|
||||
payable_account = get_payable_account("Wind Power LLC")
|
||||
payable_account = get_payable_account(company_name)
|
||||
taxes = generate_taxes()
|
||||
expense_claim = make_expense_claim(payable_account, 300, 200, "Wind Power LLC", "Travel Expenses - WP", do_not_submit=True, taxes=taxes)
|
||||
expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", do_not_submit=True, taxes=taxes)
|
||||
expense_claim.submit()
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
@ -78,9 +81,9 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = dict((d[0], d) for d in [
|
||||
['CGST - WP',10.0, 0.0],
|
||||
[payable_account, 0.0, 210.0],
|
||||
["Travel Expenses - WP", 200.0, 0.0]
|
||||
['CGST - _TC4',18.0, 0.0],
|
||||
[payable_account, 0.0, 218.0],
|
||||
["Travel Expenses - _TC4", 200.0, 0.0]
|
||||
])
|
||||
|
||||
for gle in gl_entries:
|
||||
@ -89,14 +92,14 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
self.assertEquals(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_rejected_expense_claim(self):
|
||||
payable_account = get_payable_account("Wind Power LLC")
|
||||
payable_account = get_payable_account(company_name)
|
||||
expense_claim = frappe.get_doc({
|
||||
"doctype": "Expense Claim",
|
||||
"employee": "_T-Employee-00001",
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Rejected",
|
||||
"expenses":
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "amount": 300, "sanctioned_amount": 200 }]
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
|
||||
})
|
||||
expense_claim.submit()
|
||||
|
||||
@ -111,9 +114,9 @@ def get_payable_account(company):
|
||||
|
||||
def generate_taxes():
|
||||
parent_account = frappe.db.get_value('Account',
|
||||
{'company': "Wind Power LLC", 'is_group':1, 'account_type': 'Tax'},
|
||||
{'company': company_name, 'is_group':1, 'account_type': 'Tax'},
|
||||
'name')
|
||||
account = create_account(company="Wind Power LLC", account_name="CGST", account_type="Tax", parent_account=parent_account)
|
||||
account = create_account(company=company_name, account_name="CGST", account_type="Tax", parent_account=parent_account)
|
||||
return {'taxes':[{
|
||||
"account_head": account,
|
||||
"rate": 0,
|
||||
@ -124,15 +127,18 @@ def generate_taxes():
|
||||
|
||||
def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None):
|
||||
employee = frappe.db.get_value("Employee", {"status": "Active"})
|
||||
currency = frappe.db.get_value('Company', company, 'default_currency')
|
||||
expense_claim = {
|
||||
"doctype": "Expense Claim",
|
||||
"employee": employee,
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Approved",
|
||||
"company": company,
|
||||
'currency': currency,
|
||||
"expenses":
|
||||
[{"expense_type": "Travel",
|
||||
"default_account": account,
|
||||
'currency': currency,
|
||||
"amount": amount,
|
||||
"sanctioned_amount": sanctioned_amount}]}
|
||||
if taxes:
|
||||
|
@ -615,6 +615,7 @@ erpnext.patches.v11_1.set_missing_opportunity_from
|
||||
erpnext.patches.v12_0.set_quotation_status
|
||||
erpnext.patches.v12_0.set_priority_for_support
|
||||
erpnext.patches.v12_0.delete_priority_property_setter
|
||||
execute:frappe.delete_doc("DocType", "Project Task")
|
||||
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
|
||||
erpnext.patches.v12_0.update_due_date_in_gle
|
||||
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
|
||||
|
@ -2,10 +2,9 @@ import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Task')
|
||||
frappe.reload_doctype('Project Task')
|
||||
|
||||
# add "Completed" if customized
|
||||
for doctype in ('Task', 'Project Task'):
|
||||
for doctype in ('Task'):
|
||||
property_setter_name = frappe.db.exists('Property Setter', dict(doc_type = doctype, field_name = 'status', property = 'options'))
|
||||
if property_setter_name:
|
||||
property_setter = frappe.get_doc('Property Setter', property_setter_name)
|
||||
|
@ -1,23 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
frappe.ui.form.on("Project", {
|
||||
setup: function (frm) {
|
||||
frm.set_indicator_formatter('title',
|
||||
function (doc) {
|
||||
let indicator = 'orange';
|
||||
if (doc.status == 'Overdue') {
|
||||
indicator = 'red';
|
||||
} else if (doc.status == 'Cancelled') {
|
||||
indicator = 'dark grey';
|
||||
} else if (doc.status == 'Completed') {
|
||||
indicator = 'green';
|
||||
}
|
||||
return indicator;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
onload: function (frm) {
|
||||
var so = frappe.meta.get_docfield("Project", "sales_order");
|
||||
so.get_route_options_for_new_doc = function (field) {
|
||||
@ -99,58 +82,4 @@ frappe.ui.form.on("Project", {
|
||||
});
|
||||
},
|
||||
|
||||
tasks_refresh: function (frm) {
|
||||
var grid = frm.get_field('tasks').grid;
|
||||
grid.wrapper.find('select[data-fieldname="status"]').each(function () {
|
||||
if ($(this).val() === 'Open') {
|
||||
$(this).addClass('input-indicator-open');
|
||||
} else {
|
||||
$(this).removeClass('input-indicator-open');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
status: function(frm) {
|
||||
if (frm.doc.status === 'Cancelled') {
|
||||
frappe.confirm(__('Set tasks in this project as cancelled?'), () => {
|
||||
frm.doc.tasks = frm.doc.tasks.map(task => {
|
||||
task.status = 'Cancelled';
|
||||
return task;
|
||||
});
|
||||
frm.refresh_field('tasks');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Project Task", {
|
||||
edit_task: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
if(doc.task_id) {
|
||||
frappe.set_route("Form", "Task", doc.task_id);
|
||||
} else {
|
||||
frappe.msgprint(__("Save the document first."));
|
||||
}
|
||||
},
|
||||
|
||||
edit_timesheet: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
frappe.route_options = {"project": frm.doc.project_name, "task": child.task_id};
|
||||
frappe.set_route("List", "Timesheet");
|
||||
},
|
||||
|
||||
make_timesheet: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
frappe.model.with_doctype('Timesheet', function() {
|
||||
var doc = frappe.model.get_new_doc('Timesheet');
|
||||
var row = frappe.model.add_child(doc, 'time_logs');
|
||||
row.project = frm.doc.project_name;
|
||||
row.task = child.task_id;
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
})
|
||||
},
|
||||
|
||||
status: function(frm, doctype, name) {
|
||||
frm.trigger('tasks_refresh');
|
||||
},
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,10 +19,6 @@ class Project(Document):
|
||||
return '{0}: {1}'.format(_(self.status), frappe.safe_decode(self.project_name))
|
||||
|
||||
def onload(self):
|
||||
"""Load project tasks for quick view"""
|
||||
if not self.get('__unsaved') and not self.get("tasks"):
|
||||
self.load_tasks()
|
||||
|
||||
self.set_onload('activity_summary', frappe.db.sql('''select activity_type,
|
||||
sum(hours) as total_hours
|
||||
from `tabTimesheet Detail` where project=%s and docstatus < 2 group by activity_type
|
||||
@ -33,57 +29,19 @@ class Project(Document):
|
||||
def before_print(self):
|
||||
self.onload()
|
||||
|
||||
def load_tasks(self):
|
||||
"""Load `tasks` from the database"""
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
project_task_custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task"}, "fieldname")
|
||||
|
||||
self.tasks = []
|
||||
for task in self.get_tasks():
|
||||
task_map = {
|
||||
"title": task.subject,
|
||||
"status": task.status,
|
||||
"start_date": task.exp_start_date,
|
||||
"end_date": task.exp_end_date,
|
||||
"description": task.description,
|
||||
"task_id": task.name,
|
||||
"task_weight": task.task_weight
|
||||
}
|
||||
|
||||
self.map_custom_fields(task, task_map, project_task_custom_fields)
|
||||
|
||||
self.append("tasks", task_map)
|
||||
|
||||
def get_tasks(self):
|
||||
if self.name is None:
|
||||
return {}
|
||||
else:
|
||||
filters = {"project": self.name}
|
||||
|
||||
if self.get("deleted_task_list"):
|
||||
filters.update({
|
||||
'name': ("not in", self.deleted_task_list)
|
||||
})
|
||||
|
||||
return frappe.get_all("Task", "*", filters, order_by="exp_start_date asc, status asc")
|
||||
|
||||
def validate(self):
|
||||
self.validate_weights()
|
||||
self.sync_tasks()
|
||||
self.tasks = []
|
||||
self.load_tasks()
|
||||
if not self.is_new():
|
||||
self.copy_from_template()
|
||||
self.validate_dates()
|
||||
self.send_welcome_email()
|
||||
self.update_percent_complete(from_validate=True)
|
||||
self.update_costing()
|
||||
self.update_percent_complete()
|
||||
|
||||
def copy_from_template(self):
|
||||
'''
|
||||
Copy tasks from template
|
||||
'''
|
||||
if self.project_template and not len(self.tasks or []):
|
||||
if self.project_template and not frappe.db.get_all('Task', dict(project = self.name), limit=1):
|
||||
|
||||
# has a template, and no loaded tasks, so lets create
|
||||
if not self.expected_start_date:
|
||||
@ -108,104 +66,6 @@ class Project(Document):
|
||||
task_weight = task.task_weight
|
||||
)).insert()
|
||||
|
||||
# reload tasks after project
|
||||
self.load_tasks()
|
||||
|
||||
def validate_dates(self):
|
||||
if self.tasks:
|
||||
for d in self.tasks:
|
||||
if self.expected_start_date:
|
||||
if d.start_date and getdate(d.start_date) < getdate(self.expected_start_date):
|
||||
frappe.throw(_("Start date of task <b>{0}</b> cannot be less than <b>{1}</b> expected start date <b>{2}</b>")
|
||||
.format(d.title, self.name, self.expected_start_date))
|
||||
if d.end_date and getdate(d.end_date) < getdate(self.expected_start_date):
|
||||
frappe.throw(_("End date of task <b>{0}</b> cannot be less than <b>{1}</b> expected start date <b>{2}</b>")
|
||||
.format(d.title, self.name, self.expected_start_date))
|
||||
|
||||
if self.expected_end_date:
|
||||
if d.start_date and getdate(d.start_date) > getdate(self.expected_end_date):
|
||||
frappe.throw(_("Start date of task <b>{0}</b> cannot be greater than <b>{1}</b> expected end date <b>{2}</b>")
|
||||
.format(d.title, self.name, self.expected_end_date))
|
||||
if d.end_date and getdate(d.end_date) > getdate(self.expected_end_date):
|
||||
frappe.throw(_("End date of task <b>{0}</b> cannot be greater than <b>{1}</b> expected end date <b>{2}</b>")
|
||||
.format(d.title, self.name, self.expected_end_date))
|
||||
|
||||
if self.expected_start_date and self.expected_end_date:
|
||||
if getdate(self.expected_end_date) < getdate(self.expected_start_date):
|
||||
frappe.throw(_("Expected End Date can not be less than Expected Start Date"))
|
||||
|
||||
def validate_weights(self):
|
||||
for task in self.tasks:
|
||||
if task.task_weight is not None:
|
||||
if task.task_weight < 0:
|
||||
frappe.throw(_("Task weight cannot be negative"))
|
||||
|
||||
def sync_tasks(self):
|
||||
"""sync tasks and remove table"""
|
||||
if not hasattr(self, "deleted_task_list"):
|
||||
self.set("deleted_task_list", [])
|
||||
|
||||
if self.flags.dont_sync_tasks: return
|
||||
task_names = []
|
||||
|
||||
existing_task_data = {}
|
||||
|
||||
fields = ["title", "status", "start_date", "end_date", "description", "task_weight", "task_id"]
|
||||
exclude_fieldtype = ["Button", "Column Break",
|
||||
"Section Break", "Table", "Read Only", "Attach", "Attach Image", "Color", "Geolocation", "HTML", "Image"]
|
||||
|
||||
custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task",
|
||||
"fieldtype": ("not in", exclude_fieldtype)}, "fieldname")
|
||||
|
||||
for d in custom_fields:
|
||||
fields.append(d.fieldname)
|
||||
|
||||
for d in frappe.get_all('Project Task',
|
||||
fields = fields,
|
||||
filters = {'parent': self.name}):
|
||||
existing_task_data.setdefault(d.task_id, d)
|
||||
|
||||
for t in self.tasks:
|
||||
if t.task_id:
|
||||
task = frappe.get_doc("Task", t.task_id)
|
||||
else:
|
||||
task = frappe.new_doc("Task")
|
||||
task.project = self.name
|
||||
|
||||
if not t.task_id or self.is_row_updated(t, existing_task_data, fields):
|
||||
task.update({
|
||||
"subject": t.title,
|
||||
"status": t.status,
|
||||
"exp_start_date": t.start_date,
|
||||
"exp_end_date": t.end_date,
|
||||
"description": t.description,
|
||||
"task_weight": t.task_weight
|
||||
})
|
||||
|
||||
self.map_custom_fields(t, task, custom_fields)
|
||||
|
||||
task.flags.ignore_links = True
|
||||
task.flags.from_project = True
|
||||
task.flags.ignore_feed = True
|
||||
|
||||
if t.task_id:
|
||||
task.update({
|
||||
"modified_by": frappe.session.user,
|
||||
"modified": now()
|
||||
})
|
||||
|
||||
task.run_method("validate")
|
||||
task.db_update()
|
||||
else:
|
||||
task.save(ignore_permissions = True)
|
||||
task_names.append(task.name)
|
||||
else:
|
||||
task_names.append(task.name)
|
||||
|
||||
# delete
|
||||
for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
|
||||
self.deleted_task_list.append(t.name)
|
||||
|
||||
def is_row_updated(self, row, existing_task_data, fields):
|
||||
if self.get("__islocal") or not existing_task_data: return True
|
||||
|
||||
@ -215,48 +75,43 @@ class Project(Document):
|
||||
if row.get(field) != d.get(field):
|
||||
return True
|
||||
|
||||
def map_custom_fields(self, source, target, custom_fields):
|
||||
for field in custom_fields:
|
||||
target.update({
|
||||
field.fieldname: source.get(field.fieldname)
|
||||
})
|
||||
|
||||
def update_project(self):
|
||||
'''Called externally by Task'''
|
||||
self.update_percent_complete()
|
||||
self.update_costing()
|
||||
self.db_update()
|
||||
|
||||
def after_insert(self):
|
||||
self.copy_from_template()
|
||||
if self.sales_order:
|
||||
frappe.db.set_value("Sales Order", self.sales_order, "project", self.name)
|
||||
|
||||
def update_percent_complete(self, from_validate=False):
|
||||
if not self.tasks: return
|
||||
total = frappe.db.sql("""select count(name) from tabTask where project=%s""", self.name)[0][0]
|
||||
def update_percent_complete(self):
|
||||
total = frappe.db.count('Task', dict(project=self.name))
|
||||
|
||||
if not total and self.percent_complete:
|
||||
if not total:
|
||||
self.percent_complete = 0
|
||||
else:
|
||||
if (self.percent_complete_method == "Task Completion" and total > 0) or (
|
||||
not self.percent_complete_method and total > 0):
|
||||
completed = frappe.db.sql("""select count(name) from tabTask where
|
||||
project=%s and status in ('Cancelled', 'Completed')""", self.name)[0][0]
|
||||
self.percent_complete = flt(flt(completed) / total * 100, 2)
|
||||
|
||||
if (self.percent_complete_method == "Task Completion" and total > 0) or (
|
||||
not self.percent_complete_method and total > 0):
|
||||
completed = frappe.db.sql("""select count(name) from tabTask where
|
||||
project=%s and status in ('Cancelled', 'Completed')""", self.name)[0][0]
|
||||
self.percent_complete = flt(flt(completed) / total * 100, 2)
|
||||
if (self.percent_complete_method == "Task Progress" and total > 0):
|
||||
progress = frappe.db.sql("""select sum(progress) from tabTask where
|
||||
project=%s""", self.name)[0][0]
|
||||
self.percent_complete = flt(flt(progress) / total, 2)
|
||||
|
||||
if (self.percent_complete_method == "Task Progress" and total > 0):
|
||||
progress = frappe.db.sql("""select sum(progress) from tabTask where
|
||||
project=%s""", self.name)[0][0]
|
||||
self.percent_complete = flt(flt(progress) / total, 2)
|
||||
|
||||
if (self.percent_complete_method == "Task Weight" and total > 0):
|
||||
weight_sum = frappe.db.sql("""select sum(task_weight) from tabTask where
|
||||
project=%s""", self.name)[0][0]
|
||||
weighted_progress = frappe.db.sql("""select progress, task_weight from tabTask where
|
||||
project=%s""", self.name, as_dict=1)
|
||||
pct_complete = 0
|
||||
for row in weighted_progress:
|
||||
pct_complete += row["progress"] * frappe.utils.safe_div(row["task_weight"], weight_sum)
|
||||
self.percent_complete = flt(flt(pct_complete), 2)
|
||||
if (self.percent_complete_method == "Task Weight" and total > 0):
|
||||
weight_sum = frappe.db.sql("""select sum(task_weight) from tabTask where
|
||||
project=%s""", self.name)[0][0]
|
||||
weighted_progress = frappe.db.sql("""select progress, task_weight from tabTask where
|
||||
project=%s""", self.name, as_dict=1)
|
||||
pct_complete = 0
|
||||
for row in weighted_progress:
|
||||
pct_complete += row["progress"] * frappe.utils.safe_div(row["task_weight"], weight_sum)
|
||||
self.percent_complete = flt(flt(pct_complete), 2)
|
||||
|
||||
# don't update status if it is cancelled
|
||||
if self.status == 'Cancelled':
|
||||
@ -268,9 +123,6 @@ class Project(Document):
|
||||
else:
|
||||
self.status = "Open"
|
||||
|
||||
if not from_validate:
|
||||
self.db_update()
|
||||
|
||||
def update_costing(self):
|
||||
from_time_sheet = frappe.db.sql("""select
|
||||
sum(costing_amount) as costing_amount,
|
||||
@ -297,7 +149,6 @@ class Project(Document):
|
||||
self.update_sales_amount()
|
||||
self.update_billed_amount()
|
||||
self.calculate_gross_margin()
|
||||
self.db_update()
|
||||
|
||||
def calculate_gross_margin(self):
|
||||
expense_amount = (flt(self.total_costing_amount) + flt(self.total_expense_claim)
|
||||
@ -348,57 +199,6 @@ class Project(Document):
|
||||
content=content.format(*messages))
|
||||
user.welcome_email_sent = 1
|
||||
|
||||
def on_update(self):
|
||||
self.delete_task()
|
||||
self.load_tasks()
|
||||
self.update_project()
|
||||
self.update_dependencies_on_duplicated_project()
|
||||
|
||||
def delete_task(self):
|
||||
if not self.get('deleted_task_list'): return
|
||||
|
||||
for d in self.get('deleted_task_list'):
|
||||
# unlink project
|
||||
frappe.db.set_value('Task', d, 'project', '')
|
||||
|
||||
self.deleted_task_list = []
|
||||
|
||||
def update_dependencies_on_duplicated_project(self):
|
||||
if self.flags.dont_sync_tasks: return
|
||||
if not self.copied_from:
|
||||
self.copied_from = self.name
|
||||
|
||||
if self.name != self.copied_from and self.get('__unsaved'):
|
||||
# duplicated project
|
||||
dependency_map = {}
|
||||
for task in self.tasks:
|
||||
_task = frappe.db.get_value(
|
||||
'Task',
|
||||
{"subject": task.title, "project": self.copied_from},
|
||||
['name', 'depends_on_tasks'],
|
||||
as_dict=True
|
||||
)
|
||||
|
||||
if _task is None:
|
||||
continue
|
||||
|
||||
name = _task.name
|
||||
|
||||
dependency_map[task.title] = [x['subject'] for x in frappe.get_list(
|
||||
'Task Depends On', {"parent": name}, ['subject'])]
|
||||
|
||||
for key, value in iteritems(dependency_map):
|
||||
task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name })
|
||||
|
||||
task_doc = frappe.get_doc('Task', task_name)
|
||||
|
||||
for dt in value:
|
||||
dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name})
|
||||
task_doc.append('depends_on', {"task": dt_name})
|
||||
|
||||
task_doc.db_update()
|
||||
|
||||
|
||||
def get_timeline_data(doctype, name):
|
||||
'''Return timeline for attendance'''
|
||||
return dict(frappe.db.sql('''select unix_timestamp(from_time), count(*)
|
||||
|
@ -19,18 +19,18 @@ class TestProject(unittest.TestCase):
|
||||
|
||||
project = get_project('Test Project with Template')
|
||||
|
||||
project.load_tasks()
|
||||
tasks = frappe.get_all('Task', '*', dict(project=project.name), order_by='creation asc')
|
||||
|
||||
task1 = project.tasks[0]
|
||||
self.assertEqual(task1.title, 'Task 1')
|
||||
task1 = tasks[0]
|
||||
self.assertEqual(task1.subject, 'Task 1')
|
||||
self.assertEqual(task1.description, 'Task 1 description')
|
||||
self.assertEqual(getdate(task1.start_date), getdate('2019-01-01'))
|
||||
self.assertEqual(getdate(task1.end_date), getdate('2019-01-04'))
|
||||
self.assertEqual(getdate(task1.exp_start_date), getdate('2019-01-01'))
|
||||
self.assertEqual(getdate(task1.exp_end_date), getdate('2019-01-04'))
|
||||
|
||||
self.assertEqual(len(project.tasks), 4)
|
||||
task4 = project.tasks[3]
|
||||
self.assertEqual(task4.title, 'Task 4')
|
||||
self.assertEqual(getdate(task4.end_date), getdate('2019-01-06'))
|
||||
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 get_project(name):
|
||||
template = get_project_template()
|
||||
|
@ -1,12 +1,6 @@
|
||||
[
|
||||
{
|
||||
"project_name": "_Test Project",
|
||||
"status": "Open",
|
||||
"tasks":[
|
||||
{
|
||||
"title": "_Test Task",
|
||||
"status": "Open"
|
||||
}
|
||||
]
|
||||
"status": "Open"
|
||||
}
|
||||
]
|
@ -1,430 +0,0 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2015-02-22 11:15:28.201059",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"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": "Title",
|
||||
"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": 3,
|
||||
"default": "Open",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"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": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Open\nWorking\nPending Review\nOverdue\nCompleted\nCancelled",
|
||||
"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,
|
||||
"depends_on": "task_id",
|
||||
"fieldname": "edit_task",
|
||||
"fieldtype": "Button",
|
||||
"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": "View Task",
|
||||
"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": "edit_timesheet",
|
||||
"fieldtype": "Button",
|
||||
"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": "View Timesheet",
|
||||
"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": "make_timesheet",
|
||||
"fieldtype": "Button",
|
||||
"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": "Make Timesheet",
|
||||
"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": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"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,
|
||||
"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": 2,
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Start Date",
|
||||
"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": 2,
|
||||
"default": "",
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "End Date",
|
||||
"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": "task_weight",
|
||||
"fieldtype": "Float",
|
||||
"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": "Weight",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"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": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"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,
|
||||
"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": 0,
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "task_id",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"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 ID",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Task",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 1,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-19 12:30:52.648868",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Project Task",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# 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 ProjectTask(Document):
|
||||
pass
|
@ -158,12 +158,6 @@ class Task(NestedSet):
|
||||
if check_if_child_exists(self.name):
|
||||
throw(_("Child Task exists for this Task. You can not delete this Task."))
|
||||
|
||||
if self.project:
|
||||
tasks = frappe.get_doc('Project', self.project).tasks
|
||||
for task in tasks:
|
||||
if task.get('task_id') == self.name:
|
||||
frappe.delete_doc('Project Task', task.name)
|
||||
|
||||
self.update_nsm_model()
|
||||
|
||||
def update_status(self):
|
||||
|
@ -547,12 +547,6 @@ def make_project(source_name, target_doc=None):
|
||||
"base_grand_total" : "estimated_costing",
|
||||
}
|
||||
},
|
||||
"Sales Order Item": {
|
||||
"doctype": "Project Task",
|
||||
"field_map": {
|
||||
"item_code": "title",
|
||||
},
|
||||
}
|
||||
}, target_doc, postprocess)
|
||||
|
||||
return doc
|
||||
|
Loading…
x
Reference in New Issue
Block a user