From 50234cb0fe0d362dc2393c0a0f9bda7d27d9b619 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 31 Mar 2015 10:59:29 +0530 Subject: [PATCH] Task added to expense claim all cost (expense claim and time log) against Task; task updates project cost. --- .../doctype/expense_claim/expense_claim.json | 12 ++- .../hr/doctype/expense_claim/expense_claim.py | 17 ++-- erpnext/projects/doctype/project/project.json | 6 +- erpnext/projects/doctype/task/task.js | 13 +++ erpnext/projects/doctype/task/task.json | 92 ++++++++++++------- erpnext/projects/doctype/task/task.py | 28 +++--- .../projects/doctype/time_log/time_log.json | 44 ++++----- erpnext/projects/doctype/time_log/time_log.py | 35 +++++-- 8 files changed, 160 insertions(+), 87 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index ef3d6170bf..7392138025 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -191,6 +191,14 @@ "permlevel": 0, "precision": "" }, + { + "fieldname": "task", + "fieldtype": "Link", + "label": "Task", + "options": "Task", + "permlevel": 0, + "precision": "" + }, { "fieldname": "email_id", "fieldtype": "Data", @@ -219,8 +227,8 @@ ], "icon": "icon-money", "idx": 1, - "is_submittable": 1, - "modified": "2015-03-26 04:41:50.473196", + "is_submittable": 1, + "modified": "2015-03-30 05:17:43.963137", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index c86c3efbc6..886a55db17 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -21,17 +21,18 @@ class ExpenseClaim(Document): self.validate_sanctioned_amount() self.validate_exp_details() self.validate_expense_approver() + self.validate_task() set_employee_name(self) def on_submit(self): if self.approval_status=="Draft": frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'""")) - if self.project: - self.update_project() + if self.task: + self.update_task() def on_cancel(self): if self.project: - self.update_project() + self.update_task() def validate_exp_details(self): if not self.get('expenses'): @@ -42,10 +43,14 @@ class ExpenseClaim(Document): frappe.throw(_("{0} ({1}) must have role 'Expense Approver'")\ .format(get_fullname(self.exp_approver), self.exp_approver), InvalidExpenseApproverError) - def update_project(self): + def update_task(self): expense_amount = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` - where project = %s and approval_status = "Approved" and docstatus=1""",self.project) - frappe.db.set_value("Project", self.project, "total_expense_claims", expense_amount) + where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.task)) + frappe.db.set_value("Project", self.project, "total_expense_claim", expense_amount) + + def validate_task(self): + if self.project and not self.task: + frappe.throw(_("Task is Mandatory if Time Log is against a project")) def validate_sanctioned_amount(self): if self.total_sanctioned_amount > self.total_claimed_amount: diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index f392bc8288..90b4e65123 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -206,9 +206,9 @@ "read_only": 1 }, { - "fieldname": "total_expense_claims", + "fieldname": "total_expense_claim", "fieldtype": "Currency", - "label": "Total Expense Claims", + "label": "Total Expense Claim", "permlevel": 0, "precision": "", "read_only": 1 @@ -278,7 +278,7 @@ "icon": "icon-puzzle-piece", "idx": 1, "max_attachments": 4, - "modified": "2015-03-23 06:44:19.538443", + "modified": "2015-03-30 08:42:33.940104", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 51aabca93c..975633186b 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -25,6 +25,19 @@ erpnext.projects.Task = frappe.ui.form.Controller.extend({ this.frm.doc.project && frappe.model.remove_from_locals("Project", this.frm.doc.project); }, + + refresh: function(doc) { + if(!doc.__islocal) { + cur_frm.add_custom_button(__("Time Logs"), function() { + frappe.route_options = {"project": doc.project, "task": doc.name} + frappe.set_route("List", "Time Log"); + }, "icon-list", true); + cur_frm.add_custom_button(__("Expense Claims"), function() { + frappe.route_options = {"project": doc.project, "task": doc.name} + frappe.set_route("List", "Expense Claim"); + }, "icon-list", true); + } + } }); diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index e54783406c..5cbef3c5ad 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -6,16 +6,6 @@ "doctype": "DocType", "document_type": "Master", "fields": [ - { - "fieldname": "task_details", - "fieldtype": "Section Break", - "label": "", - "oldfieldtype": "Section Break", - "permlevel": 0, - "print_width": "50%", - "search_index": 0, - "width": "50%" - }, { "fieldname": "subject", "fieldtype": "Data", @@ -110,28 +100,27 @@ { "fieldname": "time_and_budget", "fieldtype": "Section Break", - "label": "Time and Budget", + "label": "", "oldfieldtype": "Section Break", "permlevel": 0 }, { - "fieldname": "expected", - "fieldtype": "Column Break", - "label": "Expected", - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_width": "50%", - "width": "50%" - }, - { - "fieldname": "exp_total_hrs", - "fieldtype": "Data", - "label": "Total Hours (Expected)", + "default": "0", + "description": "in Hours", + "fieldname": "expected_time", + "fieldtype": "Float", + "label": "Expected Time", "oldfieldname": "exp_total_hrs", "oldfieldtype": "Data", "permlevel": 0, "reqd": 0 }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "allocated_budget", "fieldtype": "Currency", @@ -143,8 +132,8 @@ }, { "fieldname": "actual", - "fieldtype": "Column Break", - "label": "Actual", + "fieldtype": "Section Break", + "label": "", "oldfieldtype": "Column Break", "permlevel": 0, "print_width": "50%", @@ -156,7 +145,14 @@ "label": "Actual Start Date", "oldfieldname": "act_start_date", "oldfieldtype": "Date", - "permlevel": 0 + "permlevel": 0, + "read_only": 1 + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" }, { "fieldname": "act_end_date", @@ -164,16 +160,50 @@ "label": "Actual End Date", "oldfieldname": "act_end_date", "oldfieldtype": "Date", - "permlevel": 0 + "permlevel": 0, + "read_only": 1 }, { - "fieldname": "actual_budget", + "fieldname": "section_break_17", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "default": "0", + "description": "in Hours", + "fieldname": "actual_time", + "fieldtype": "Float", + "label": "Actual Time", + "options": "", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "actual_cost", "fieldtype": "Currency", - "label": "Actual Budget", + "label": "Actual Cost", "oldfieldname": "actual_budget", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0 + "permlevel": 0, + "read_only": 1 + }, + { + "fieldname": "total_expense_claim", + "fieldtype": "Currency", + "label": "Total Expense Claim", + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "read_only": 1 }, { "fieldname": "more_details", @@ -217,7 +247,7 @@ "icon": "icon-check", "idx": 1, "max_attachments": 5, - "modified": "2015-02-20 05:09:27.295024", + "modified": "2015-03-30 05:50:04.409614", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index acd087768d..c921d02897 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, json -from frappe.utils import getdate, today +from frappe.utils import getdate from frappe import _ @@ -26,27 +26,33 @@ class Task(Document): return ret def validate(self): + self.validate_dates() + + 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(_("'Expected Start Date' can not be greater than '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(_("'Actual Start Date' can not be greater than 'Actual End Date'")) - self.update_status() - - def update_status(self): - status = frappe.db.get_value("Task", self.name, "status") - if self.status=="Working" and status !="Working" and not self.act_start_date: - self.act_start_date = today() - - if self.status=="Closed" and status != "Closed" and not self.act_end_date: - self.act_end_date = today() - def on_update(self): + self.update_percentage() + self.update_project() + + def update_percentage(self): """update percent complete in project""" if self.project and not self.flags.from_project: project = frappe.get_doc("Project", self.project) project.run_method("update_percent_complete") + + def update_project(self): + total_activity_cost = frappe.db.sql("""select sum(actual_cost) from `tabTask` + where project = %s""",self.project) + frappe.db.set_value("Project", self.project, "total_activity_cost", total_activity_cost) + + total_expense_claim = frappe.db.sql("""select sum(total_expense_claim) from `tabTask` + where project = %s""",self.project) + frappe.db.set_value("Project", self.project, "total_expense_claim", total_expense_claim) @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 8b7c956f36..f52bcad3bc 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -91,6 +91,25 @@ "precision": "", "read_only": 1 }, + { + "depends_on": "", + "fieldname": "project", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Project", + "options": "Project", + "permlevel": 0, + "read_only": 0 + }, + { + "depends_on": "", + "fieldname": "task", + "fieldtype": "Link", + "label": "Task", + "options": "Task", + "permlevel": 0, + "read_only": 0 + }, { "depends_on": "eval:!doc.for_manufacturing", "fieldname": "activity_type", @@ -102,15 +121,6 @@ "read_only": 0, "reqd": 0 }, - { - "depends_on": "eval:!doc.for_manufacturing", - "fieldname": "task", - "fieldtype": "Link", - "label": "Task", - "options": "Task", - "permlevel": 0, - "read_only": 0 - }, { "depends_on": "eval:doc.for_manufacturing", "fieldname": "section_break_11", @@ -188,22 +198,6 @@ "permlevel": 0, "read_only": 0 }, - { - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "permlevel": 0, - "read_only": 0 - }, - { - "depends_on": "", - "fieldname": "project", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Project", - "options": "Project", - "permlevel": 0, - "read_only": 0 - }, { "depends_on": "", "fieldname": "section_break_24", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index de37912328..09d0becf73 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -24,15 +24,20 @@ class TimeLog(Document): self.check_workstation_timings() self.validate_production_order() self.validate_manufacturing() + self.validate_task() self.validate_cost() def on_submit(self): - self.update_production_order() - self.update_project() + if self.for_manufacturing: + self.update_production_order() + if self.task: + self.update_task() def on_cancel(self): - self.update_production_order() - self.update_project() + if self.for_manufacturing: + self.update_production_order() + if self.task: + self.update_task() def before_update_after_submit(self): self.set_status() @@ -128,7 +133,7 @@ class TimeLog(Document): def update_production_order(self): """Updates `start_date`, `end_date`, `status` for operation in Production Order.""" - if self.for_manufacturing and self.production_order: + if self.production_order: if not self.operation_id: frappe.throw(_("Operation ID not set")) @@ -217,10 +222,22 @@ class TimeLog(Document): else: self.billing_amount = 0 - def update_project(self): - activity_cost = frappe.db.sql("""select sum(billing_cost) from `tabTime Log` - where project = %s and docstatus=1""",self.project) - frappe.db.set_value("Project", self.project, "total_activity_cost", activity_cost) + def validate_task(self): + if self.project and not self.task: + frappe.throw(_("Task is Mandatory if Time Log is against a project")) + + def update_task(self): + tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, sum(billing_amount) as cost, sum(hours) as time + from `tabTime Log` where project = %s and task = %s and docstatus=1""",(self.project, self.task),as_dict=1)[0] + + task = frappe.get_doc("Task", self.task) + if task.status == "Open": + task.status = "Working" + task.actual_cost= tl.cost + task.actual_time= tl.time + task.act_start_date= tl.start_date + task.act_end_date= tl.end_date + task.save() @frappe.whitelist() def get_events(start, end, filters=None):