From 458b8885f09d31d6e5ad3d68f8b2f7f58d904f66 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 24 Mar 2015 12:49:45 +0530 Subject: [PATCH 01/27] New Document Activity Cost created. In TL - billing rate and internal rate added In Project - total expense claim and total activity cost. Buttons addded - show time logs and show expense claims. In Expense Claim - Employee and project link added. update cost in project on submit and on trash. Validation added to check sanctioned amount is not greater than claim amount. --- erpnext/config/projects.py | 5 + .../hr/doctype/expense_claim/expense_claim.py | 16 ++ .../doctype/activity_cost/__init__.py | 0 .../doctype/activity_cost/activity_cost.json | 146 ++++++++++++++++++ .../doctype/activity_cost/activity_cost.py | 10 ++ .../activity_cost/test_activity_cost.py | 12 ++ erpnext/projects/doctype/project/project.js | 8 + erpnext/projects/doctype/project/project.json | 18 ++- .../projects/doctype/time_log/time_log.json | 34 +++- erpnext/projects/doctype/time_log/time_log.py | 1 + 10 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 erpnext/projects/doctype/activity_cost/__init__.py create mode 100644 erpnext/projects/doctype/activity_cost/activity_cost.json create mode 100644 erpnext/projects/doctype/activity_cost/activity_cost.py create mode 100644 erpnext/projects/doctype/activity_cost/test_activity_cost.py diff --git a/erpnext/config/projects.py b/erpnext/config/projects.py index d591c9e2a8..07149e38b6 100644 --- a/erpnext/config/projects.py +++ b/erpnext/config/projects.py @@ -32,6 +32,11 @@ def get_data(): "name": "Activity Type", "description": _("Types of activities for Time Sheets"), }, + { + "type": "doctype", + "name": "Activity Cost", + "description": _("Cost of various activities"), + }, ] }, { diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 76606e5b8a..c86c3efbc6 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -18,6 +18,7 @@ class ExpenseClaim(Document): def validate(self): validate_fiscal_year(self.posting_date, self.fiscal_year, _("Posting Date"), self) + self.validate_sanctioned_amount() self.validate_exp_details() self.validate_expense_approver() set_employee_name(self) @@ -25,6 +26,12 @@ class ExpenseClaim(Document): def on_submit(self): if self.approval_status=="Draft": frappe.throw(_("""Approval Status must be 'Approved' or 'Rejected'""")) + if self.project: + self.update_project() + + def on_cancel(self): + if self.project: + self.update_project() def validate_exp_details(self): if not self.get('expenses'): @@ -34,3 +41,12 @@ class ExpenseClaim(Document): if self.exp_approver and "Expense Approver" not in frappe.get_roles(self.exp_approver): frappe.throw(_("{0} ({1}) must have role 'Expense Approver'")\ .format(get_fullname(self.exp_approver), self.exp_approver), InvalidExpenseApproverError) + + def update_project(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) + + def validate_sanctioned_amount(self): + if self.total_sanctioned_amount > self.total_claimed_amount: + frappe.throw(_("Total sanctioned amount cannot be greater than total claimed amount.")) \ No newline at end of file diff --git a/erpnext/projects/doctype/activity_cost/__init__.py b/erpnext/projects/doctype/activity_cost/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.json b/erpnext/projects/doctype/activity_cost/activity_cost.json new file mode 100644 index 0000000000..48c1247ab6 --- /dev/null +++ b/erpnext/projects/doctype/activity_cost/activity_cost.json @@ -0,0 +1,146 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 1, + "creation": "2015-03-23 02:00:21.861546", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Employee", + "no_copy": 0, + "options": "Employee", + "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": "" + }, + { + "allow_on_submit": 0, + "fieldname": "activity_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Activity Type", + "no_copy": 0, + "options": "Activity Type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "billing_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Billing Rate", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "intrernal_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Internal Rate", + "no_copy": 0, + "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": 0, + "modified": "2015-03-23 02:01:53.789728", + "modified_by": "Administrator", + "module": "Projects", + "name": "Activity Cost", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py new file mode 100644 index 0000000000..31df89aa66 --- /dev/null +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class ActivityCost(Document): + pass diff --git a/erpnext/projects/doctype/activity_cost/test_activity_cost.py b/erpnext/projects/doctype/activity_cost/test_activity_cost.py new file mode 100644 index 0000000000..6f483dec5b --- /dev/null +++ b/erpnext/projects/doctype/activity_cost/test_activity_cost.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Activity Cost') + +class TestActivityCost(unittest.TestCase): + pass diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index eeedeb0147..56cc09dbee 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -21,6 +21,14 @@ cur_frm.cscript.refresh = function(doc) { frappe.route_options = {"project": doc.name} frappe.set_route("List", "Task"); }, "icon-list", true); + cur_frm.add_custom_button(__("Time Logs"), function() { + frappe.route_options = {"project": doc.name} + frappe.set_route("List", "Time Log"); + }, "icon-list", true); + cur_frm.add_custom_button(__("Expense Claims"), function() { + frappe.route_options = {"project": doc.name} + frappe.set_route("List", "Expense Claim"); + }, "icon-list", true); } } diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 6a143ab083..f392bc8288 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -197,6 +197,22 @@ "permlevel": 0, "search_index": 0 }, + { + "fieldname": "total_activity_cost", + "fieldtype": "Currency", + "label": "Total Activity Cost", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "total_expense_claims", + "fieldtype": "Currency", + "label": "Total Expense Claims", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "fieldname": "cost_center", "fieldtype": "Link", @@ -262,7 +278,7 @@ "icon": "icon-puzzle-piece", "idx": 1, "max_attachments": 4, - "modified": "2015-02-22 11:17:49.051755", + "modified": "2015-03-23 06:44:19.538443", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 20b5c0c296..217bc65960 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -50,6 +50,14 @@ "permlevel": 0, "precision": "" }, + { + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "permlevel": 0, + "precision": "" + }, { "fieldname": "column_break_3", "fieldtype": "Column Break", @@ -195,6 +203,26 @@ "permlevel": 0, "read_only": 0 }, + { + "fieldname": "billing_rate", + "fieldtype": "Currency", + "label": "Billing Rate", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "internal_rate", + "fieldtype": "Currency", + "label": "Internal Rate", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "column_break_25", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "description": "Will be updated when batched.", "fieldname": "time_log_batch", @@ -213,12 +241,6 @@ "permlevel": 0, "read_only": 1 }, - { - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "permlevel": 0, - "read_only": 0 - }, { "fieldname": "amended_from", "fieldtype": "Link", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index c385c0935b..36bf5a7f6c 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -63,6 +63,7 @@ class TimeLog(Document): def validate_overlap(self): """Checks if 'Time Log' entries overlap for a user, workstation. """ self.validate_overlap_for("user") + self.validate_overlap_for("employee") self.validate_overlap_for("workstation") def validate_overlap_for(self, fieldname): From 80261379982e762a2b70f64b500cdcc95c666dbc Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 26 Mar 2015 19:19:11 +0530 Subject: [PATCH 02/27] employee name added to activity cost title changed for activity type time log -fetch logic added for billing and internal rate validations and calculation logic added --- .../doctype/activity_cost/activity_cost.json | 34 ++++++++++++-- .../doctype/activity_cost/activity_cost.py | 8 +++- .../doctype/project_task/project_task.json | 4 +- erpnext/projects/doctype/time_log/time_log.js | 42 ++++++++++++++++- .../projects/doctype/time_log/time_log.json | 47 +++++++++++++++++-- erpnext/projects/doctype/time_log/time_log.py | 22 +++++++++ 6 files changed, 143 insertions(+), 14 deletions(-) diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.json b/erpnext/projects/doctype/activity_cost/activity_cost.json index 48c1247ab6..0f37c5c747 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.json +++ b/erpnext/projects/doctype/activity_cost/activity_cost.json @@ -2,6 +2,7 @@ "allow_copy": 0, "allow_import": 1, "allow_rename": 1, + "autoname": "Activity Cost - .#", "creation": "2015-03-23 02:00:21.861546", "custom": 0, "docstatus": 0, @@ -24,10 +25,18 @@ "print_hide": 0, "read_only": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0 }, + { + "fieldname": "employee_name", + "fieldtype": "Read Only", + "label": "Employee Name", + "options": "employee.employee_name", + "permlevel": 0, + "precision": "" + }, { "fieldname": "column_break_2", "fieldtype": "Column Break", @@ -50,7 +59,7 @@ "print_hide": 0, "read_only": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0 }, @@ -62,6 +71,8 @@ }, { "allow_on_submit": 0, + "default": "0", + "description": "per hour", "fieldname": "billing_rate", "fieldtype": "Currency", "hidden": 0, @@ -87,7 +98,9 @@ }, { "allow_on_submit": 0, - "fieldname": "intrernal_rate", + "default": "0", + "description": "per hour", + "fieldname": "internal_rate", "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, @@ -103,6 +116,16 @@ "reqd": 0, "search_index": 0, "set_only_once": 0 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "title", + "no_copy": 1, + "permlevel": 0, + "precision": "", + "read_only": 1 } ], "hide_heading": 0, @@ -112,7 +135,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-03-23 02:01:53.789728", + "modified": "2015-03-25 07:50:46.554655", "modified_by": "Administrator", "module": "Projects", "name": "Activity Cost", @@ -142,5 +165,6 @@ "read_only": 0, "read_only_onload": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "title_field": "title" } \ No newline at end of file diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py index 31df89aa66..efba0f6734 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -3,8 +3,12 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +from frappe import _ from frappe.model.document import Document class ActivityCost(Document): - pass + def validate(self): + self.set_title() + + def set_title(self): + self.title = _("{0} for {1}").format(self.employee_name, self.activity_type) diff --git a/erpnext/projects/doctype/project_task/project_task.json b/erpnext/projects/doctype/project_task/project_task.json index c29dcc05d6..00e97f6402 100644 --- a/erpnext/projects/doctype/project_task/project_task.json +++ b/erpnext/projects/doctype/project_task/project_task.json @@ -51,7 +51,7 @@ { "fieldname": "edit_task", "fieldtype": "Button", - "label": "Edit Task", + "label": "View Task", "permlevel": 0, "precision": "" }, @@ -143,7 +143,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-02-23 01:55:18.865117", + "modified": "2015-03-26 04:55:36.680900", "modified_by": "Administrator", "module": "Projects", "name": "Project Task", diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index a49b1f1c04..e4fbfc3ee1 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -41,4 +41,44 @@ frappe.ui.form.on("Time Log", "to_time", function(frm) { if(frm._setting_hours) return; frm.set_value("hours", moment(cur_frm.doc.to_time).diff(moment(cur_frm.doc.from_time), "hours")); -}); \ No newline at end of file + +}); + +var calculate_cost = function(doc) { + cur_frm.set_value("internal_cost", doc.internal_rate * doc.hours); + if (doc.billable==1){ + cur_frm.set_value("billing_amount", doc.billing_rate * doc.hours); + } +} + +frappe.ui.form.on("Time Log", "hours", function(frm) { + calculate_cost(frm.doc); +}); + +frappe.ui.form.on("Time Log", "activity_type", function(frm) { + return frappe.call({ + method: "erpnext.projects.doctype.time_log.time_log.get_activity_cost", + args: { + "employee": frm.doc.employee, + "activity_type": frm.doc.activity_type + }, + callback: function(r) { + if(!r.exc) { + cur_frm.set_value("internal_rate", r.message.internal_rate); + cur_frm.set_value("billing_rate", r.message.billing_rate); + calculate_cost(frm.doc); + } + } + }); +}); + +cur_frm.cscript.employee = cur_frm.cscript.activity_type; + +cur_frm.cscript.billable = function(doc) { + if (doc.billable==1) { + calculate_cost(doc); + } + else { + cur_frm.set_value("billing_amount", 0); + } +} diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 217bc65960..8b7c956f36 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -35,6 +35,7 @@ "reqd": 1 }, { + "default": "0", "fieldname": "hours", "fieldtype": "Float", "in_list_view": 1, @@ -204,18 +205,30 @@ "read_only": 0 }, { - "fieldname": "billing_rate", - "fieldtype": "Currency", - "label": "Billing Rate", + "depends_on": "", + "fieldname": "section_break_24", + "fieldtype": "Section Break", "permlevel": 0, "precision": "" }, { + "default": "0", + "description": "per hour", "fieldname": "internal_rate", "fieldtype": "Currency", "label": "Internal Rate", "permlevel": 0, - "precision": "" + "precision": "", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "internal_cost", + "fieldtype": "Currency", + "label": "Internal Cost", + "permlevel": 0, + "precision": "", + "read_only": 1 }, { "fieldname": "column_break_25", @@ -223,6 +236,32 @@ "permlevel": 0, "precision": "" }, + { + "default": "0", + "description": "per hour", + "fieldname": "billing_rate", + "fieldtype": "Currency", + "label": "Billing Rate", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "default": "0", + "description": "will be updated only if Time Log is 'Billable'", + "fieldname": "billing_amount", + "fieldtype": "Currency", + "label": "Billing Amount", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "section_break_29", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, { "description": "Will be updated when batched.", "fieldname": "time_log_batch", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 36bf5a7f6c..de37912328 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -24,12 +24,15 @@ class TimeLog(Document): self.check_workstation_timings() self.validate_production_order() self.validate_manufacturing() + self.validate_cost() def on_submit(self): self.update_production_order() + self.update_project() def on_cancel(self): self.update_production_order() + self.update_project() def before_update_after_submit(self): self.set_status() @@ -206,6 +209,18 @@ class TimeLog(Document): self.production_order = None self.operation = None self.quantity = None + + def validate_cost(self): + self.internal_cost = self.internal_rate * self.hours + if self.billable: + self.billing_amount = self.billing_rate * self.hours + 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) @frappe.whitelist() def get_events(start, end, filters=None): @@ -243,3 +258,10 @@ def get_events(start, end, filters=None): d.title += " for Project: " + d.project return data + +@frappe.whitelist() +def get_activity_cost(employee=None, activity_type=None): + internal_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "internal_rate") + billing_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "billing_rate") + return {"internal_rate": internal_rate, "billing_rate": billing_rate } + From 50234cb0fe0d362dc2393c0a0f9bda7d27d9b619 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 31 Mar 2015 10:59:29 +0530 Subject: [PATCH 03/27] 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): From a11770f06ff10b2e3dd28a78eb5aacd167f350e1 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 31 Mar 2015 13:27:55 +0530 Subject: [PATCH 04/27] in Time Log - Server side validations added to recalculate cost on save --- .../hr/doctype/expense_claim/expense_claim.py | 5 ++++- erpnext/projects/doctype/task/task.json | 3 ++- erpnext/projects/doctype/time_log/time_log.js | 18 ++++++++++++------ erpnext/projects/doctype/time_log/time_log.py | 3 +++ 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 886a55db17..9bc16e4ebc 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -46,7 +46,10 @@ class ExpenseClaim(Document): def update_task(self): expense_amount = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` 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) + + task = frappe.get_doc("Task", self.task) + task.total_expense_claim = expense_amount + task.save() def validate_task(self): if self.project and not self.task: diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 5cbef3c5ad..b46fe2111c 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -246,8 +246,9 @@ ], "icon": "icon-check", "idx": 1, + "istable": 0, "max_attachments": 5, - "modified": "2015-03-30 05:50:04.409614", + "modified": "2015-03-31 03:31:13.055284", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index e4fbfc3ee1..6892d10228 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -51,11 +51,7 @@ var calculate_cost = function(doc) { } } -frappe.ui.form.on("Time Log", "hours", function(frm) { - calculate_cost(frm.doc); -}); - -frappe.ui.form.on("Time Log", "activity_type", function(frm) { +var get_activity_cost = function(frm) { return frappe.call({ method: "erpnext.projects.doctype.time_log.time_log.get_activity_cost", args: { @@ -70,9 +66,19 @@ frappe.ui.form.on("Time Log", "activity_type", function(frm) { } } }); +} + +frappe.ui.form.on("Time Log", "hours", function(frm) { + calculate_cost(frm.doc); }); -cur_frm.cscript.employee = cur_frm.cscript.activity_type; +frappe.ui.form.on("Time Log", "activity_type", function(frm) { + get_activity_cost(frm); +}); + +frappe.ui.form.on("Time Log", "employee", function(frm) { + get_activity_cost(frm); +}); cur_frm.cscript.billable = function(doc) { if (doc.billable==1) { diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 09d0becf73..1f70221f10 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -216,6 +216,9 @@ class TimeLog(Document): self.quantity = None def validate_cost(self): + rate = get_activity_cost(self.employee, self.activity_type) + self.internal_rate = rate.get('internal_rate') + self.billing_rate = rate.get('billing_rate') self.internal_cost = self.internal_rate * self.hours if self.billable: self.billing_amount = self.billing_rate * self.hours From aabf1504234dc632a21dd9a8c258e0d3eaf565ba Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 31 Mar 2015 15:56:21 +0530 Subject: [PATCH 05/27] task made mandatory in project --- erpnext/projects/doctype/project/project.json | 5 +++-- erpnext/projects/doctype/project/project.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 90b4e65123..4490b61c74 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -130,7 +130,8 @@ "label": "Tasks", "options": "Project Task", "permlevel": 0, - "precision": "" + "precision": "", + "reqd": 0 }, { "fieldname": "percent_complete", @@ -278,7 +279,7 @@ "icon": "icon-puzzle-piece", "idx": 1, "max_attachments": 4, - "modified": "2015-03-30 08:42:33.940104", + "modified": "2015-03-31 06:12:03.372091", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index c142180fa8..1fc327f16b 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -34,11 +34,18 @@ class Project(Document): return ret def validate(self): + self.validate_dates() + self.validate_tasks() + self.sync_tasks() + + def validate_dates(self): if self.project_start_date and self.completion_date: if getdate(self.completion_date) < getdate(self.project_start_date): frappe.throw(_("Expected Completion Date can not be less than Project Start Date")) - - self.sync_tasks() + + def validate_tasks(self): + if not self.tasks: + frappe.throw("Task is mandatory against a project") def sync_tasks(self): """sync tasks and remove table""" From 709c43674b88c6feb48b856e63da8c7f25608f7c Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Apr 2015 12:08:18 +0530 Subject: [PATCH 06/27] feild rename - internal rate/cost into costing rate/amount --- .../projects/doctype/activity_cost/activity_cost.json | 6 +++--- erpnext/projects/doctype/time_log/time_log.js | 4 ++-- erpnext/projects/doctype/time_log/time_log.json | 8 ++++---- erpnext/projects/doctype/time_log/time_log.py | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.json b/erpnext/projects/doctype/activity_cost/activity_cost.json index 0f37c5c747..b44e3106be 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.json +++ b/erpnext/projects/doctype/activity_cost/activity_cost.json @@ -100,13 +100,13 @@ "allow_on_submit": 0, "default": "0", "description": "per hour", - "fieldname": "internal_rate", + "fieldname": "costing_rate", "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Internal Rate", + "label": "Costing Rate", "no_copy": 0, "permlevel": 0, "precision": "", @@ -135,7 +135,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-03-25 07:50:46.554655", + "modified": "2015-04-01 02:06:37.510007", "modified_by": "Administrator", "module": "Projects", "name": "Activity Cost", diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index 6892d10228..e1fa91b4ac 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -45,7 +45,7 @@ frappe.ui.form.on("Time Log", "to_time", function(frm) { }); var calculate_cost = function(doc) { - cur_frm.set_value("internal_cost", doc.internal_rate * doc.hours); + cur_frm.set_value("costing_amount", doc.costing_rate * doc.hours); if (doc.billable==1){ cur_frm.set_value("billing_amount", doc.billing_rate * doc.hours); } @@ -60,7 +60,7 @@ var get_activity_cost = function(frm) { }, callback: function(r) { if(!r.exc) { - cur_frm.set_value("internal_rate", r.message.internal_rate); + cur_frm.set_value("costing_rate", r.message.costing_rate); cur_frm.set_value("billing_rate", r.message.billing_rate); calculate_cost(frm.doc); } diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index f52bcad3bc..92ae856a62 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -208,18 +208,18 @@ { "default": "0", "description": "per hour", - "fieldname": "internal_rate", + "fieldname": "costing_rate", "fieldtype": "Currency", - "label": "Internal Rate", + "label": "Costing Rate", "permlevel": 0, "precision": "", "read_only": 1 }, { "default": "0", - "fieldname": "internal_cost", + "fieldname": "costing_amount", "fieldtype": "Currency", - "label": "Internal Cost", + "label": "Costing Amount", "permlevel": 0, "precision": "", "read_only": 1 diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 1f70221f10..fc6ae5331b 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -217,9 +217,9 @@ class TimeLog(Document): def validate_cost(self): rate = get_activity_cost(self.employee, self.activity_type) - self.internal_rate = rate.get('internal_rate') - self.billing_rate = rate.get('billing_rate') - self.internal_cost = self.internal_rate * self.hours + self.costing_rate = rate.get('costing_rate') or 0 + self.billing_rate = rate.get('billing_rate') or 0 + self.costing_amount = self.costing_rate * self.hours if self.billable: self.billing_amount = self.billing_rate * self.hours else: @@ -281,7 +281,7 @@ def get_events(start, end, filters=None): @frappe.whitelist() def get_activity_cost(employee=None, activity_type=None): - internal_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "internal_rate") + costing_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "costing_rate") billing_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "billing_rate") - return {"internal_rate": internal_rate, "billing_rate": billing_rate } + return {"costing_rate": costing_rate, "billing_rate": billing_rate } From 33d58ac4318295a3e1c324dae7a1bf8529a1ac39 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 7 Apr 2015 16:16:20 +0530 Subject: [PATCH 07/27] Test Cases --- .../doctype/activity_cost/activity_cost.py | 7 +++ .../activity_cost/test_activity_cost.py | 2 +- .../doctype/activity_cost/test_records.json | 9 +++ .../projects/doctype/project/test_project.py | 1 - .../doctype/project/test_records.json | 14 +++-- .../doctype/project_task/project_task.json | 4 +- erpnext/projects/doctype/task/task.py | 15 ++--- .../projects/doctype/task/test_records.json | 4 +- erpnext/projects/doctype/task/test_task.py | 2 - .../doctype/time_log/test_records.json | 3 +- .../doctype/time_log/test_time_log.py | 58 ++++++++++++++++++- 11 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 erpnext/projects/doctype/activity_cost/test_records.json diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py index efba0f6734..03aff28a29 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -3,12 +3,19 @@ # For license information, please see license.txt from __future__ import unicode_literals +import frappe from frappe import _ from frappe.model.document import Document class ActivityCost(Document): def validate(self): self.set_title() + self.check_unique() def set_title(self): self.title = _("{0} for {1}").format(self.employee_name, self.activity_type) + + def check_unique(self): + if frappe.db.exists({ "doctype": "Activity Cost", "employee": self.employee, "activity_type": self.activity_type }): + frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type {1}") + .format(self.employee, self.activity_type)) diff --git a/erpnext/projects/doctype/activity_cost/test_activity_cost.py b/erpnext/projects/doctype/activity_cost/test_activity_cost.py index 6f483dec5b..e9ec0b3aa3 100644 --- a/erpnext/projects/doctype/activity_cost/test_activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/test_activity_cost.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import unittest -# test_records = frappe.get_test_records('Activity Cost') +test_records = frappe.get_test_records('Activity Cost') class TestActivityCost(unittest.TestCase): pass diff --git a/erpnext/projects/doctype/activity_cost/test_records.json b/erpnext/projects/doctype/activity_cost/test_records.json new file mode 100644 index 0000000000..30c9c9e0d6 --- /dev/null +++ b/erpnext/projects/doctype/activity_cost/test_records.json @@ -0,0 +1,9 @@ +[ + { + "employee": "_T-Employee-0001", + "employee_name": "_Test Employee", + "activity_type": "_Test Activity Type", + "billing_rate": 100, + "costing_rate": 50 + } +] \ No newline at end of file diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index 744d6b427b..f69ce80824 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -5,4 +5,3 @@ from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Project') -test_ignore = ["Task"] diff --git a/erpnext/projects/doctype/project/test_records.json b/erpnext/projects/doctype/project/test_records.json index 69226f0cc9..9379c22b5c 100644 --- a/erpnext/projects/doctype/project/test_records.json +++ b/erpnext/projects/doctype/project/test_records.json @@ -1,10 +1,12 @@ [ { - "project_name": "_Test Project", - "status": "Open" - }, - { - "project_name": "_Test Project 1", - "status": "Open" + "project_name": "_Test Project", + "status": "Open", + "tasks":[ + { + "title": "_Test Task", + "status": "Open" + } + ] } ] \ No newline at end of file diff --git a/erpnext/projects/doctype/project_task/project_task.json b/erpnext/projects/doctype/project_task/project_task.json index 00e97f6402..11d5b55168 100644 --- a/erpnext/projects/doctype/project_task/project_task.json +++ b/erpnext/projects/doctype/project_task/project_task.json @@ -128,7 +128,7 @@ { "fieldname": "task_id", "fieldtype": "Link", - "hidden": 1, + "hidden": 0, "label": "Task ID", "no_copy": 1, "options": "Task", @@ -143,7 +143,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-03-26 04:55:36.680900", + "modified": "2015-04-06 09:41:50.911955", "modified_by": "Administrator", "module": "Projects", "name": "Project Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index c921d02897..6748eadcbf 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -46,13 +46,14 @@ class Task(Document): 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) + if self.project: + 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/task/test_records.json b/erpnext/projects/doctype/task/test_records.json index 1f98172b70..42ca0e77ea 100644 --- a/erpnext/projects/doctype/task/test_records.json +++ b/erpnext/projects/doctype/task/test_records.json @@ -1,8 +1,8 @@ [ { - "project": "_Test Project", "status": "Open", - "subject": "_Test Task" + "subject": "_Test Task", + "name": "task001" }, { "status": "Open", diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 6f75e0deb8..62e560fbe7 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -5,5 +5,3 @@ from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Task') -test_dependencies = ["Project"] -test_ignore = ["Customer"] diff --git a/erpnext/projects/doctype/time_log/test_records.json b/erpnext/projects/doctype/time_log/test_records.json index d9e67e954a..568c0121a7 100644 --- a/erpnext/projects/doctype/time_log/test_records.json +++ b/erpnext/projects/doctype/time_log/test_records.json @@ -5,7 +5,6 @@ "doctype": "Time Log", "from_time": "2013-01-01 10:00:00.000000", "note": "_Test Note", - "to_time": "2013-01-01 11:00:00.000000", - "project": "_Test Project" + "to_time": "2013-01-01 11:00:00.000000" } ] diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index 8f8e31d01a..630962d670 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -10,7 +10,6 @@ from erpnext.projects.doctype.time_log.time_log import OverlapError from erpnext.projects.doctype.time_log.time_log import NotSubmittedError from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError from erpnext.manufacturing.doctype.workstation.workstation import NotInWorkingHoursError -from erpnext.projects.doctype.time_log_batch.test_time_log_batch import * from erpnext.manufacturing.doctype.production_order.test_production_order import make_prod_order_test_record @@ -85,6 +84,61 @@ class TestTimeLog(unittest.TestCase): test_time_log.to_time = "2013-01-01 10:00:00.000000" self.assertRaises(frappe.ValidationError, test_time_log.save) frappe.db.sql("delete from `tabTime Log`") - + + def test_time_log_costing(self): + frappe.db.sql("delete from `tabTask`") + frappe.db.sql("delete from `tabProject`") + + frappe.get_doc({ + "project_name": "_Test Project 1", + "doctype": "Project", + "tasks" : + [{ "title": "_Test Project Task 1", "status": "Open" }] + }).save() + + task_name = frappe.db.get_value("Task",{"project": "_Test Project 1"}) + + time_log = frappe.get_doc({ + "activity_type": "_Test Activity Type", + "docstatus": 1, + "doctype": "Time Log", + "from_time": "2013-02-02 09:00:00.000000", + "to_time": "2013-02-02 11:00:00.000000", + "employee": "_T-Employee-0001", + "project": "_Test Project 1", + "task": task_name, + "billable": 1 + }) + time_log.save() + self.assertEqual(time_log.costing_rate, 50) + self.assertEqual(time_log.costing_amount, 100) + self.assertEqual(time_log.billing_rate, 100) + self.assertEqual(time_log.billing_amount, 200) + time_log.submit() + + self.assertEqual(frappe.db.get_value("Task", task_name, "actual_cost"), 200) + self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_activity_cost"), 200) + + time_log2 = frappe.get_doc({ + "activity_type": "_Test Activity Type", + "docstatus": 1, + "doctype": "Time Log", + "from_time": "2013-02-03 09:00:00.000000", + "to_time": "2013-02-03 11:00:00.000000", + "employee": "_T-Employee-0001", + "project": "_Test Project 1", + "task": task_name, + "billable": 1 + }) + time_log2.save() + + self.assertEqual(frappe.db.get_value("Task", task_name, "actual_cost"), 400) + self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_activity_cost"), 400) + + time_log2.cancel() + + self.assertEqual(frappe.db.get_value("Task", task_name, "actual_cost"), 200) + self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_activity_cost"), 200) + test_records = frappe.get_test_records('Time Log') test_ignore = ["Time Log Batch", "Sales Invoice"] From 7f9ea3bf6ad0706bfe50f86f570aaee637761ba3 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 7 Apr 2015 18:11:49 +0530 Subject: [PATCH 08/27] expense claim test records --- .../hr/doctype/expense_claim/expense_claim.py | 12 ++++- .../expense_claim/test_expense_claim.py | 48 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 9bc16e4ebc..eb297f827b 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -18,8 +18,9 @@ class ExpenseClaim(Document): def validate(self): validate_fiscal_year(self.posting_date, self.fiscal_year, _("Posting Date"), self) - self.validate_sanctioned_amount() self.validate_exp_details() + self.validate_cost() + self.validate_sanctioned_amount() self.validate_expense_approver() self.validate_task() set_employee_name(self) @@ -33,6 +34,15 @@ class ExpenseClaim(Document): def on_cancel(self): if self.project: self.update_task() + + def validate_cost(self): + total_claimed_amount = 0 + total_sanctioned_amount = 0 + for d in self.expenses: + total_claimed_amount += d.claim_amount + total_sanctioned_amount += d.sanctioned_amount + self.total_claimed_amount = total_claimed_amount + self.total_sanctioned_amount = total_sanctioned_amount def validate_exp_details(self): if not self.get('expenses'): diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index f1c657adb8..9c053839de 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -8,4 +8,50 @@ import unittest test_records = frappe.get_test_records('Expense Claim') class TestExpenseClaim(unittest.TestCase): - pass + def test_project_costing(self): + frappe.db.sql("delete from `tabTask`") + frappe.db.sql("delete from `tabProject`") + + frappe.get_doc({ + "project_name": "_Test Project 1", + "doctype": "Project", + "tasks" : + [{ "title": "_Test Project Task 1", "status": "Open" }] + }).save() + + task_name = frappe.db.get_value("Task",{"project": "_Test Project 1"}) + expense_claim = frappe.get_doc({ + "doctype": "Expense Claim", + "employee": "_T-Employee-0001", + "posting_date": "2015-07-07", + "fiscal_year": "_Test Fiscal Year 2015", + "approval_status": "Approved", + "project": "_Test Project 1", + "task": task_name, + "expenses": + [{ "expense_type": "Food", "claim_amount": 300, "sanctioned_amount": 200 }] + }) + expense_claim.submit() + + 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 = frappe.get_doc({ + "doctype": "Expense Claim", + "employee": "_T-Employee-0001", + "posting_date": "2015-07-07", + "approval_status": "Approved", + "project": "_Test Project 1", + "task": task_name, + "expenses": + [{ "expense_type": "Food", "claim_amount": 600, "sanctioned_amount": 500 }] + }) + expense_claim2.submit() + + 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) + + expense_claim2.cancel() + + 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) From 2679b247b3137647ac30783c5765c2d4a90eaa34 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 8 Apr 2015 17:00:31 +0530 Subject: [PATCH 09/27] fixes in Expense Claim --- .../hr/doctype/expense_claim/expense_claim.py | 28 ++++++++----------- .../expense_claim/test_expense_claim.py | 2 +- .../expense_claim_detail.json | 26 ++++++++++++++++- erpnext/projects/doctype/task/task.py | 4 +++ 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index eb297f827b..fb720b025e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -19,7 +19,7 @@ class ExpenseClaim(Document): def validate(self): validate_fiscal_year(self.posting_date, self.fiscal_year, _("Posting Date"), self) self.validate_exp_details() - self.validate_cost() + self.calculate_total_amount() self.validate_sanctioned_amount() self.validate_expense_approver() self.validate_task() @@ -32,17 +32,15 @@ class ExpenseClaim(Document): self.update_task() def on_cancel(self): - if self.project: + if self.task: self.update_task() - def validate_cost(self): - total_claimed_amount = 0 - total_sanctioned_amount = 0 + def calculate_total_amount(self): + self.total_claimed_amount = 0 + self.total_sanctioned_amount = 0 for d in self.expenses: - total_claimed_amount += d.claim_amount - total_sanctioned_amount += d.sanctioned_amount - self.total_claimed_amount = total_claimed_amount - self.total_sanctioned_amount = total_sanctioned_amount + self.total_claimed_amount += d.claim_amount + self.total_sanctioned_amount += d.sanctioned_amount def validate_exp_details(self): if not self.get('expenses'): @@ -54,17 +52,15 @@ class ExpenseClaim(Document): .format(get_fullname(self.exp_approver), self.exp_approver), InvalidExpenseApproverError) def update_task(self): - expense_amount = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` - where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.task)) - task = frappe.get_doc("Task", self.task) - task.total_expense_claim = expense_amount + task.update_total_expense_claim() task.save() def validate_task(self): if self.project and not self.task: - frappe.throw(_("Task is Mandatory if Time Log is against a project")) + frappe.throw(_("Task is mandatory if Expense Claim is against a Project")) def validate_sanctioned_amount(self): - if self.total_sanctioned_amount > self.total_claimed_amount: - frappe.throw(_("Total sanctioned amount cannot be greater than total claimed amount.")) \ No newline at end of file + for d in self.expenses: + if d.sanctioned_amount > d.claim_amount: + frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx)) \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 9c053839de..286754866d 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -8,7 +8,7 @@ import unittest test_records = frappe.get_test_records('Expense Claim') class TestExpenseClaim(unittest.TestCase): - def test_project_costing(self): + def test_total_expense_claim_for_project(self): frappe.db.sql("delete from `tabTask`") frappe.db.sql("delete from `tabProject`") diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json index d97518941e..c6123eeeff 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json @@ -15,6 +15,12 @@ "reqd": 0, "width": "150px" }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "expense_type", "fieldtype": "Link", @@ -28,6 +34,12 @@ "reqd": 1, "width": "150px" }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "description", "fieldtype": "Small Text", @@ -39,6 +51,12 @@ "print_width": "300px", "width": "300px" }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "claim_amount", "fieldtype": "Currency", @@ -52,6 +70,12 @@ "reqd": 1, "width": "150px" }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "allow_on_submit": 0, "fieldname": "sanctioned_amount", @@ -69,7 +93,7 @@ ], "idx": 1, "istable": 1, - "modified": "2014-05-09 02:16:38.529082", + "modified": "2015-04-08 06:18:47.539134", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim Detail", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 6748eadcbf..4e29d17ad9 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -45,6 +45,10 @@ class Task(Document): project = frappe.get_doc("Project", self.project) project.run_method("update_percent_complete") + 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 approval_status = "Approved" and docstatus=1""",(self.project, self.name)) + def update_project(self): if self.project: total_activity_cost = frappe.db.sql("""select sum(actual_cost) from `tabTask` From c33ad6b36a21abed90b463039f74c4bdf2465156 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 8 Apr 2015 17:42:38 +0530 Subject: [PATCH 10/27] fixes in Activity Cost --- erpnext/projects/doctype/activity_cost/activity_cost.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py index 03aff28a29..fb2d6666b9 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -16,6 +16,7 @@ class ActivityCost(Document): self.title = _("{0} for {1}").format(self.employee_name, self.activity_type) def check_unique(self): - if frappe.db.exists({ "doctype": "Activity Cost", "employee": self.employee, "activity_type": self.activity_type }): - frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type {1}") - .format(self.employee, self.activity_type)) + if frappe.db.sql("""select name from `tabActivity Cost` where employee_name= %s and activity_type= %s and name != %s""", + (self.employee_name, self.activity_type, self.name)): + frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type - {1}") + .format(self.employee, self.activity_type)) From 78b96ca76993ed6ed2ee96b8f911142725733c20 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 9 Apr 2015 18:28:32 +0530 Subject: [PATCH 11/27] Fixes in time_log --- erpnext/projects/doctype/task/task.py | 11 +++++++ .../doctype/time_log/test_time_log.py | 2 +- erpnext/projects/doctype/time_log/time_log.js | 10 +++--- .../projects/doctype/time_log/time_log.json | 6 +++- erpnext/projects/doctype/time_log/time_log.py | 33 ++++++++----------- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 4e29d17ad9..fdd225d77a 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -49,6 +49,17 @@ class Task(Document): self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.name)) + def update_actual_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 cost, sum(hours) as time from `tabTime Log` where + project = %s and task = %s and docstatus=1""",(self.project, self.name),as_dict=1)[0] + if self.status == "Open": + self.status = "Working" + self.actual_cost= tl.cost + self.actual_time= tl.time + self.act_start_date= tl.start_date + self.act_end_date= tl.end_date + def update_project(self): if self.project: total_activity_cost = frappe.db.sql("""select sum(actual_cost) from `tabTask` diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index 630962d670..fad24335e8 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -85,7 +85,7 @@ class TestTimeLog(unittest.TestCase): self.assertRaises(frappe.ValidationError, test_time_log.save) frappe.db.sql("delete from `tabTime Log`") - def test_time_log_costing(self): + def test_total_activity_cost_for_project(self): frappe.db.sql("delete from `tabTask`") frappe.db.sql("delete from `tabProject`") diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index e1fa91b4ac..5625ff3975 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -80,11 +80,11 @@ frappe.ui.form.on("Time Log", "employee", function(frm) { get_activity_cost(frm); }); -cur_frm.cscript.billable = function(doc) { - if (doc.billable==1) { - calculate_cost(doc); +frappe.ui.form.on("Time Log", "billable", function(frm) { + if (frm.doc.billable==1) { + calculate_cost(frm.doc); } else { - cur_frm.set_value("billing_amount", 0); + frm.doc("billing_amount", 0); } -} +}); diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 92ae856a62..a2852aedba 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -242,7 +242,7 @@ }, { "default": "0", - "description": "will be updated only if Time Log is 'Billable'", + "description": "Will be updated only if Time Log is 'Billable'", "fieldname": "billing_amount", "fieldtype": "Currency", "label": "Billing Amount", @@ -297,7 +297,11 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, +<<<<<<< HEAD "modified": "2015-04-06 02:47:16.187046", +======= + "modified": "2015-04-09 08:29:34.464429", +>>>>>>> Fixes in time_log "modified_by": "Administrator", "module": "Projects", "name": "Time Log", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index fc6ae5331b..34b244faf9 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -217,29 +217,22 @@ class TimeLog(Document): def validate_cost(self): rate = get_activity_cost(self.employee, self.activity_type) - self.costing_rate = rate.get('costing_rate') or 0 - self.billing_rate = rate.get('billing_rate') or 0 - self.costing_amount = self.costing_rate * self.hours - if self.billable: - self.billing_amount = self.billing_rate * self.hours - else: - self.billing_amount = 0 + if rate: + self.costing_rate = rate.get('costing_rate') + self.billing_rate = rate.get('billing_rate') + self.costing_amount = self.costing_rate * self.hours + if self.billable: + self.billing_amount = self.billing_rate * self.hours + else: + self.billing_amount = 0 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.update_actual_time_and_costing() task.save() @frappe.whitelist() @@ -281,7 +274,7 @@ def get_events(start, end, filters=None): @frappe.whitelist() def get_activity_cost(employee=None, activity_type=None): - costing_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "costing_rate") - billing_rate = frappe.db.get_value("Activity Cost", {"employee":employee,"activity_type":activity_type}, "billing_rate") - return {"costing_rate": costing_rate, "billing_rate": billing_rate } - + rate = frappe.db.sql("""select costing_rate, billing_rate from `tabActivity Cost` where employee= %s + and activity_type= %s""", (employee, activity_type), as_dict=1) + if rate: + return {"costing_rate": rate[0].costing_rate, "billing_rate": rate[0].billing_rate } From 650e679a5e44e234fbe0e1618514e0fc1a6f3f2d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 10 Apr 2015 14:44:41 +0530 Subject: [PATCH 12/27] Fixes in Project --- erpnext/projects/doctype/project/project.json | 4 ++-- erpnext/projects/doctype/project/project.py | 8 -------- erpnext/projects/doctype/task/task.py | 13 +++++-------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 4490b61c74..62210b80c3 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -131,7 +131,7 @@ "options": "Project Task", "permlevel": 0, "precision": "", - "reqd": 0 + "reqd": 1 }, { "fieldname": "percent_complete", @@ -279,7 +279,7 @@ "icon": "icon-puzzle-piece", "idx": 1, "max_attachments": 4, - "modified": "2015-03-31 06:12:03.372091", + "modified": "2015-04-10 05:11:18.849171", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 1fc327f16b..b481ee422a 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -35,17 +35,12 @@ class Project(Document): def validate(self): self.validate_dates() - self.validate_tasks() self.sync_tasks() def validate_dates(self): if self.project_start_date and self.completion_date: if getdate(self.completion_date) < getdate(self.project_start_date): frappe.throw(_("Expected Completion Date can not be less than Project Start Date")) - - def validate_tasks(self): - if not self.tasks: - frappe.throw("Task is mandatory against a project") def sync_tasks(self): """sync tasks and remove table""" @@ -74,8 +69,6 @@ class Project(Document): for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}): frappe.delete_doc("Task", t.name) - self.tasks = [] - def update_percent_complete(self): total = frappe.db.sql("""select count(*) from tabTask where project=%s""", self.name)[0][0] @@ -85,7 +78,6 @@ class Project(Document): frappe.db.set_value("Project", self.name, "percent_complete", int(float(completed) / total * 100)) - @frappe.whitelist() def get_cost_center_name(project_name): return frappe.db.get_value("Project", project_name, "cost_center") diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index fdd225d77a..617809954d 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -61,14 +61,11 @@ class Task(Document): self.act_end_date= tl.end_date def update_project(self): - if self.project: - 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) + if self.project and frappe.db.exists("Project", self.project): + total_cost = frappe.db.sql("""select sum(actual_cost) as actual_cost, sum(total_expense_claim) as expense_claim + from `tabTask` where project = %s""", self.project, as_dict=1)[0] + frappe.db.set_value("Project", self.project, "total_activity_cost", total_cost.actual_cost) + frappe.db.set_value("Project", self.project, "total_expense_claim", total_cost.expense_claim) @frappe.whitelist() def get_events(start, end, filters=None): From 9789d06b3e0cdbfead4fbe927fb3dfff4068dd0b Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 13 Apr 2015 19:01:34 +0530 Subject: [PATCH 13/27] task and project- billing amount and actual amount added --- erpnext/projects/doctype/project/project.json | 159 ++++++++++++------ erpnext/projects/doctype/project/project.py | 8 +- .../doctype/project_task/project_task.json | 6 +- erpnext/projects/doctype/task/task.json | 49 +++--- erpnext/projects/doctype/task/task.py | 26 ++- .../projects/doctype/time_log/time_log.json | 129 ++++++++------ erpnext/projects/doctype/time_log/time_log.py | 2 +- 7 files changed, 242 insertions(+), 137 deletions(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 62210b80c3..ef6140a37a 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -14,12 +14,6 @@ "options": "icon-file", "permlevel": 0 }, - { - "fieldname": "cb_project_status", - "fieldtype": "Column Break", - "label": "Status", - "permlevel": 0 - }, { "description": "", "fieldname": "project_name", @@ -45,6 +39,23 @@ "reqd": 1, "search_index": 1 }, + { + "fieldname": "project_type", + "fieldtype": "Select", + "label": "Project Type", + "no_copy": 0, + "oldfieldname": "project_type", + "oldfieldtype": "Data", + "options": "Internal\nExternal\nOther", + "permlevel": 0, + "search_index": 0 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "is_active", "fieldtype": "Select", @@ -69,16 +80,16 @@ "search_index": 0 }, { - "fieldname": "cb_project_dates", - "fieldtype": "Column Break", - "label": "Dates", - "permlevel": 0 + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" }, { - "fieldname": "project_start_date", + "fieldname": "expected_start_date", "fieldtype": "Date", "in_filter": 1, - "label": "Project Start Date", + "label": "Expected Start Date", "no_copy": 0, "oldfieldname": "project_start_date", "oldfieldtype": "Date", @@ -86,36 +97,21 @@ "search_index": 0 }, { - "fieldname": "completion_date", + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "expected_end_date", "fieldtype": "Date", - "label": "Completion Date", + "label": "Expected End Date", "no_copy": 0, "oldfieldname": "completion_date", "oldfieldtype": "Date", "permlevel": 0, "search_index": 0 }, - { - "fieldname": "act_completion_date", - "fieldtype": "Date", - "label": "Actual Completion Date", - "no_copy": 0, - "oldfieldname": "act_completion_date", - "oldfieldtype": "Date", - "permlevel": 0, - "search_index": 0 - }, - { - "fieldname": "project_type", - "fieldtype": "Select", - "label": "Project Type", - "no_copy": 0, - "oldfieldname": "project_type", - "oldfieldtype": "Data", - "options": "Internal\nExternal\nOther", - "permlevel": 0, - "search_index": 0 - }, { "fieldname": "sb_milestones", "fieldtype": "Section Break", @@ -131,7 +127,7 @@ "options": "Project Task", "permlevel": 0, "precision": "", - "reqd": 1 + "reqd": 0 }, { "fieldname": "percent_complete", @@ -166,6 +162,45 @@ "options": "Company", "permlevel": 0 }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "actual_start_date", + "fieldtype": "Data", + "label": "Actual Start Date", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "actual_time", + "fieldtype": "Float", + "label": "Actual Time", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "actual_end_date", + "fieldtype": "Date", + "label": "Actual End Date", + "no_copy": 0, + "oldfieldname": "act_completion_date", + "oldfieldtype": "Date", + "permlevel": 0, + "read_only": 1, + "search_index": 0 + }, { "fieldname": "project_details", "fieldtype": "Section Break", @@ -188,20 +223,9 @@ "search_index": 0 }, { - "fieldname": "est_material_cost", + "fieldname": "total_costing_amount", "fieldtype": "Currency", - "label": "Estimated Material Cost", - "no_copy": 0, - "oldfieldname": "est_material_cost", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "search_index": 0 - }, - { - "fieldname": "total_activity_cost", - "fieldtype": "Currency", - "label": "Total Activity Cost", + "label": "Total Costing Amount", "permlevel": 0, "precision": "", "read_only": 1 @@ -214,6 +238,31 @@ "precision": "", "read_only": 1 }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "est_material_cost", + "fieldtype": "Currency", + "label": "Estimated Material Cost", + "no_copy": 0, + "oldfieldname": "est_material_cost", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "search_index": 0 + }, + { + "fieldname": "total_billing_amount", + "fieldtype": "Currency", + "label": "Total Billing Amount", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "fieldname": "cost_center", "fieldtype": "Link", @@ -222,8 +271,8 @@ "permlevel": 0 }, { - "fieldname": "column_break0", - "fieldtype": "Column Break", + "fieldname": "margin", + "fieldtype": "Section Break", "label": "Margin", "oldfieldtype": "Column Break", "permlevel": 0, @@ -241,6 +290,12 @@ "reqd": 0, "search_index": 0 }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "per_gross_margin", "fieldtype": "Currency", @@ -279,7 +334,7 @@ "icon": "icon-puzzle-piece", "idx": 1, "max_attachments": 4, - "modified": "2015-04-10 05:11:18.849171", + "modified": "2015-04-13 07:13:06.962942", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index b481ee422a..0b45e4d3bb 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -38,9 +38,9 @@ class Project(Document): self.sync_tasks() def validate_dates(self): - if self.project_start_date and self.completion_date: - if getdate(self.completion_date) < getdate(self.project_start_date): - frappe.throw(_("Expected Completion Date can not be less than Project Start 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 sync_tasks(self): """sync tasks and remove table""" @@ -68,6 +68,8 @@ class Project(Document): # delete for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}): frappe.delete_doc("Task", t.name) + + self.tasks = [] def update_percent_complete(self): total = frappe.db.sql("""select count(*) from tabTask where project=%s""", diff --git a/erpnext/projects/doctype/project_task/project_task.json b/erpnext/projects/doctype/project_task/project_task.json index 11d5b55168..f3d2c66d9b 100644 --- a/erpnext/projects/doctype/project_task/project_task.json +++ b/erpnext/projects/doctype/project_task/project_task.json @@ -37,7 +37,7 @@ "in_filter": 0, "in_list_view": 1, "label": "Status", - "no_copy": 0, + "no_copy": 1, "options": "Open\nWorking\nPending Review\nClosed\nCancelled", "permlevel": 0, "precision": "", @@ -128,7 +128,7 @@ { "fieldname": "task_id", "fieldtype": "Link", - "hidden": 0, + "hidden": 1, "label": "Task ID", "no_copy": 1, "options": "Task", @@ -143,7 +143,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-04-06 09:41:50.911955", + "modified": "2015-04-13 04:56:18.766659", "modified_by": "Administrator", "module": "Projects", "name": "Project Task", diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index b46fe2111c..664210016d 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -148,6 +148,17 @@ "permlevel": 0, "read_only": 1 }, + { + "default": "", + "description": "in Hours", + "fieldname": "actual_time", + "fieldtype": "Float", + "label": "Actual Time", + "options": "", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "fieldname": "column_break_15", "fieldtype": "Column Break", @@ -170,26 +181,9 @@ "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", + "fieldname": "total_costing_amount", "fieldtype": "Currency", - "label": "Actual Cost", + "label": "Total Costing Amount", "oldfieldname": "actual_budget", "oldfieldtype": "Currency", "options": "Company:company:default_currency", @@ -205,6 +199,21 @@ "precision": "", "read_only": 1 }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "total_billing_amount", + "fieldtype": "Currency", + "hidden": 0, + "label": "Total Billing Amount", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "fieldname": "more_details", "fieldtype": "Section Break", @@ -248,7 +257,7 @@ "idx": 1, "istable": 0, "max_attachments": 5, - "modified": "2015-03-31 03:31:13.055284", + "modified": "2015-04-13 05:36:33.544083", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 617809954d..8c74edb941 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -49,23 +49,33 @@ class Task(Document): self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.name)) - def update_actual_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 cost, sum(hours) as time from `tabTime Log` where - project = %s and task = %s and docstatus=1""",(self.project, self.name),as_dict=1)[0] + def update_time_and_costing(self): + tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, + sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount, + sum(hours) as time from `tabTime Log` where project = %s and task = %s and docstatus=1""", + (self.project, self.name),as_dict=1)[0] if self.status == "Open": self.status = "Working" - self.actual_cost= tl.cost + 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_project(self): if self.project and frappe.db.exists("Project", self.project): - total_cost = frappe.db.sql("""select sum(actual_cost) as actual_cost, sum(total_expense_claim) as expense_claim + total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount, + sum(total_billing_amount) as billing_amount, sum(total_expense_claim) as expense_claim, + min(act_start_date) as start_date, max(act_end_date) as end_date, sum(actual_time) as time from `tabTask` where project = %s""", self.project, as_dict=1)[0] - frappe.db.set_value("Project", self.project, "total_activity_cost", total_cost.actual_cost) - frappe.db.set_value("Project", self.project, "total_expense_claim", total_cost.expense_claim) + frappe.db.set_values("Project", self.project, { + "total_costing_amount": total_cost.costing_amount, + "total_billing_amount": total_cost.billing_amount, + "total_expense_claim": total_cost.expense_claim, + "actual_start_date": total_cost.start_date, + "actual_end_date": total_cost.end_date, + "actual_time": total_cost.time + }) @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 a2852aedba..09c4e87c33 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -16,6 +16,28 @@ "read_only": 0, "reqd": 1 }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 0, + "label": "Status", + "options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled", + "permlevel": 0, + "read_only": 1, + "reqd": 0 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "from_time", "fieldtype": "Datetime", @@ -25,15 +47,6 @@ "read_only": 0, "reqd": 1 }, - { - "fieldname": "to_time", - "fieldtype": "Datetime", - "in_list_view": 0, - "label": "To Time", - "permlevel": 0, - "read_only": 0, - "reqd": 1 - }, { "default": "0", "fieldname": "hours", @@ -44,52 +57,19 @@ "read_only": 0 }, { - "fieldname": "user", - "fieldtype": "Link", - "label": "User", - "options": "User", + "fieldname": "to_time", + "fieldtype": "Datetime", + "in_list_view": 0, + "label": "To Time", "permlevel": 0, - "precision": "" + "read_only": 0, + "reqd": 1 }, { - "fieldname": "employee", - "fieldtype": "Link", - "label": "Employee", - "options": "Employee", - "permlevel": 0, - "precision": "" - }, - { - "fieldname": "column_break_3", + "fieldname": "column_break_8", "fieldtype": "Column Break", "permlevel": 0, - "read_only": 0 - }, - { - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 0, - "label": "Status", - "options": "Draft\nSubmitted\nBatched for Billing\nBilled\nCancelled", - "permlevel": 0, - "read_only": 1, - "reqd": 0 - }, - { - "fieldname": "billable", - "fieldtype": "Check", - "in_list_view": 0, - "label": "Billable", - "permlevel": 0, - "read_only": 0 - }, - { - "fieldname": "for_manufacturing", - "fieldtype": "Check", - "label": "For Manufacturing", - "permlevel": 0, - "precision": "", - "read_only": 1 + "precision": "" }, { "depends_on": "", @@ -121,6 +101,51 @@ "read_only": 0, "reqd": 0 }, + { + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "permlevel": 0, + "read_only": 0 + }, + { + "fieldname": "for_manufacturing", + "fieldtype": "Check", + "hidden": 1, + "label": "For Manufacturing", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "billable", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Billable", + "permlevel": 0, + "read_only": 0 + }, { "depends_on": "eval:doc.for_manufacturing", "fieldname": "section_break_11", @@ -297,11 +322,15 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, +<<<<<<< HEAD <<<<<<< HEAD "modified": "2015-04-06 02:47:16.187046", ======= "modified": "2015-04-09 08:29:34.464429", >>>>>>> Fixes in time_log +======= + "modified": "2015-04-13 04:31:20.855439", +>>>>>>> task and project- billing amount and actual amount added "modified_by": "Administrator", "module": "Projects", "name": "Time Log", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 34b244faf9..2018425055 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -232,7 +232,7 @@ class TimeLog(Document): def update_task(self): task = frappe.get_doc("Task", self.task) - task.update_actual_time_and_costing() + task.update_time_and_costing() task.save() @frappe.whitelist() From 05f15bbb88c73b86b10136f1273a6f75764f3d68 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 14 Apr 2015 12:43:17 +0530 Subject: [PATCH 14/27] fixes in Test Cases --- .../doctype/expense_claim/test_expense_claim.py | 4 ++-- .../doctype/activity_cost/activity_cost.json | 4 ++-- .../doctype/activity_cost/activity_cost.py | 4 +++- .../doctype/activity_cost/test_activity_cost.py | 16 ++++++++++++++-- .../doctype/activity_cost/test_records.json | 10 +--------- .../projects/doctype/time_log/test_time_log.py | 16 ++++++++-------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 286754866d..f5ae93ca68 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -9,8 +9,8 @@ test_records = frappe.get_test_records('Expense Claim') class TestExpenseClaim(unittest.TestCase): def test_total_expense_claim_for_project(self): - frappe.db.sql("delete from `tabTask`") - frappe.db.sql("delete from `tabProject`") + frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """) + frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """) frappe.get_doc({ "project_name": "_Test Project 1", diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.json b/erpnext/projects/doctype/activity_cost/activity_cost.json index b44e3106be..7f7720acbb 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.json +++ b/erpnext/projects/doctype/activity_cost/activity_cost.json @@ -7,7 +7,7 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", - "document_type": "", + "document_type": "Master", "fields": [ { "allow_on_submit": 0, @@ -135,7 +135,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-04-01 02:06:37.510007", + "modified": "2015-04-14 02:08:33.690406", "modified_by": "Administrator", "module": "Projects", "name": "Activity Cost", diff --git a/erpnext/projects/doctype/activity_cost/activity_cost.py b/erpnext/projects/doctype/activity_cost/activity_cost.py index fb2d6666b9..121e6508f8 100644 --- a/erpnext/projects/doctype/activity_cost/activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/activity_cost.py @@ -7,6 +7,8 @@ import frappe from frappe import _ from frappe.model.document import Document +class DuplicationError(frappe.ValidationError): pass + class ActivityCost(Document): def validate(self): self.set_title() @@ -19,4 +21,4 @@ class ActivityCost(Document): if frappe.db.sql("""select name from `tabActivity Cost` where employee_name= %s and activity_type= %s and name != %s""", (self.employee_name, self.activity_type, self.name)): frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type - {1}") - .format(self.employee, self.activity_type)) + .format(self.employee, self.activity_type), DuplicationError) diff --git a/erpnext/projects/doctype/activity_cost/test_activity_cost.py b/erpnext/projects/doctype/activity_cost/test_activity_cost.py index e9ec0b3aa3..5afd97f96c 100644 --- a/erpnext/projects/doctype/activity_cost/test_activity_cost.py +++ b/erpnext/projects/doctype/activity_cost/test_activity_cost.py @@ -6,7 +6,19 @@ from __future__ import unicode_literals import frappe import unittest -test_records = frappe.get_test_records('Activity Cost') +from erpnext.projects.doctype.activity_cost.activity_cost import DuplicationError class TestActivityCost(unittest.TestCase): - pass + def test_duplication(self): + frappe.db.sql("delete from `tabActivity Cost`") + activity_cost1 = frappe.new_doc('Activity Cost') + activity_cost1.update({ + "employee": "_T-Employee-0001", + "employee_name": "_Test Employee", + "activity_type": "_Test Activity Type", + "billing_rate": 100, + "costing_rate": 50 + }) + activity_cost1.insert() + activity_cost2 = frappe.copy_doc(activity_cost1) + self.assertRaises(DuplicationError, activity_cost2.insert ) diff --git a/erpnext/projects/doctype/activity_cost/test_records.json b/erpnext/projects/doctype/activity_cost/test_records.json index 30c9c9e0d6..0637a088a0 100644 --- a/erpnext/projects/doctype/activity_cost/test_records.json +++ b/erpnext/projects/doctype/activity_cost/test_records.json @@ -1,9 +1 @@ -[ - { - "employee": "_T-Employee-0001", - "employee_name": "_Test Employee", - "activity_type": "_Test Activity Type", - "billing_rate": 100, - "costing_rate": 50 - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index fad24335e8..019d59694f 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -86,8 +86,8 @@ class TestTimeLog(unittest.TestCase): frappe.db.sql("delete from `tabTime Log`") def test_total_activity_cost_for_project(self): - frappe.db.sql("delete from `tabTask`") - frappe.db.sql("delete from `tabProject`") + frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """) + frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """) frappe.get_doc({ "project_name": "_Test Project 1", @@ -116,8 +116,8 @@ class TestTimeLog(unittest.TestCase): self.assertEqual(time_log.billing_amount, 200) time_log.submit() - self.assertEqual(frappe.db.get_value("Task", task_name, "actual_cost"), 200) - self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_activity_cost"), 200) + self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 200) + self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 200) time_log2 = frappe.get_doc({ "activity_type": "_Test Activity Type", @@ -132,13 +132,13 @@ class TestTimeLog(unittest.TestCase): }) time_log2.save() - self.assertEqual(frappe.db.get_value("Task", task_name, "actual_cost"), 400) - self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_activity_cost"), 400) + self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 400) + self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 400) time_log2.cancel() - self.assertEqual(frappe.db.get_value("Task", task_name, "actual_cost"), 200) - self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_activity_cost"), 200) + self.assertEqual(frappe.db.get_value("Task", task_name, "total_billing_amount"), 200) + self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_billing_amount"), 200) test_records = frappe.get_test_records('Time Log') test_ignore = ["Time Log Batch", "Sales Invoice"] From ec5c66f8f033fab5ba79e72cec3998b4f94bcdfc Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 14 Apr 2015 15:13:14 +0530 Subject: [PATCH 15/27] Title Feild added to Expense Claim --- .../hr/doctype/expense_claim/expense_claim.js | 21 +++++++++++++++++++ .../doctype/expense_claim/expense_claim.json | 13 +++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 8076bf7b60..2ba4d2257b 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -133,12 +133,14 @@ cur_frm.cscript.calculate_total = function(doc,cdt,cdn){ cur_frm.cscript.calculate_total_amount = function(doc,cdt,cdn){ cur_frm.cscript.calculate_total(doc,cdt,cdn); } + cur_frm.cscript.claim_amount = function(doc,cdt,cdn){ cur_frm.cscript.calculate_total(doc,cdt,cdn); var child = locals[cdt][cdn]; refresh_field("sanctioned_amount", child.name, child.parentfield); } + cur_frm.cscript.sanctioned_amount = function(doc,cdt,cdn){ cur_frm.cscript.calculate_total(doc,cdt,cdn); } @@ -148,3 +150,22 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { cur_frm.email_doc(frappe.boot.notification_settings.expense_claim_message); } } + +erpnext.expense_claim = { + set_title :function(frm) { + if (!frm.doc.task) { + frm.set_value("title", frm.doc.employee_name); + } + else { + frm.set_value("title", frm.doc.employee_name + " for "+ frm.doc.task); + } + } +} + +frappe.ui.form.on("Expense Claim", "employee_name", function(frm) { + erpnext.expense_claim.set_title(frm); +}); + +frappe.ui.form.on("Expense Claim", "task", function(frm) { + erpnext.expense_claim.set_title(frm); +}); diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 7392138025..95b8a81e6e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -199,6 +199,13 @@ "permlevel": 0, "precision": "" }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "permlevel": 0, + "precision": "" + }, { "fieldname": "email_id", "fieldtype": "Data", @@ -227,8 +234,8 @@ ], "icon": "icon-money", "idx": 1, - "is_submittable": 1, - "modified": "2015-03-30 05:17:43.963137", + "is_submittable": 1, + "modified": "2015-04-14 05:08:06.541441", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", @@ -302,5 +309,5 @@ "search_fields": "approval_status,employee,employee_name", "sort_field": "modified", "sort_order": "DESC", - "title_field": "employee_name" + "title_field": "title" } \ No newline at end of file From 4e6e4726a4cf6127d20b1e0e7e12f2170f95c979 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 14 Apr 2015 18:49:48 +0530 Subject: [PATCH 16/27] filters added, ui change, update project moved to project.py --- .../hr/doctype/expense_claim/expense_claim.js | 8 + erpnext/projects/doctype/project/project.json | 191 ++++++++++-------- erpnext/projects/doctype/project/project.py | 18 ++ erpnext/projects/doctype/task/task.json | 84 ++++---- erpnext/projects/doctype/task/task.py | 16 +- erpnext/projects/doctype/time_log/time_log.js | 9 +- .../projects/doctype/time_log/time_log.json | 18 +- 7 files changed, 183 insertions(+), 161 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 2ba4d2257b..d279074f84 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -169,3 +169,11 @@ frappe.ui.form.on("Expense Claim", "employee_name", function(frm) { frappe.ui.form.on("Expense Claim", "task", function(frm) { erpnext.expense_claim.set_title(frm); }); + +cur_frm.fields_dict['task'].get_query = function(doc) { + return { + filters:{ + 'project': doc.project + } + } +} \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index ef6140a37a..5c4f7d1719 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -7,13 +7,6 @@ "doctype": "DocType", "document_type": "Master", "fields": [ - { - "fieldname": "overview", - "fieldtype": "Section Break", - "label": "Overview", - "options": "icon-file", - "permlevel": 0 - }, { "description": "", "fieldname": "project_name", @@ -112,6 +105,49 @@ "permlevel": 0, "search_index": 0 }, + { + "fieldname": "customer_details", + "fieldtype": "Section Break", + "label": "", + "oldfieldtype": "Section Break", + "options": "icon-user", + "permlevel": 0 + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_filter": 1, + "label": "Customer", + "no_copy": 0, + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "permlevel": 0, + "print_hide": 1, + "reqd": 0, + "search_index": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "permlevel": 0 + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "options": "Sales Order", + "permlevel": 0, + "precision": "" + }, { "fieldname": "sb_milestones", "fieldtype": "Section Break", @@ -155,13 +191,6 @@ "permlevel": 0, "search_index": 0 }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "permlevel": 0 - }, { "fieldname": "section_break_18", "fieldtype": "Section Break", @@ -179,7 +208,7 @@ { "fieldname": "actual_time", "fieldtype": "Float", - "label": "Actual Time", + "label": "Actual Time (in Hours)", "permlevel": 0, "precision": "", "read_only": 1 @@ -202,18 +231,16 @@ "search_index": 0 }, { - "fieldname": "project_details", + "fieldname": "section_break_26", "fieldtype": "Section Break", - "label": "Project Costing", - "oldfieldtype": "Section Break", - "options": "icon-money", - "permlevel": 0 + "permlevel": 0, + "precision": "" }, { - "fieldname": "project_value", + "fieldname": "estimated_costing", "fieldtype": "Currency", "in_list_view": 1, - "label": "Project Value", + "label": "Estimated Costing", "no_copy": 0, "oldfieldname": "project_value", "oldfieldtype": "Currency", @@ -222,47 +249,12 @@ "reqd": 0, "search_index": 0 }, - { - "fieldname": "total_costing_amount", - "fieldtype": "Currency", - "label": "Total Costing Amount", - "permlevel": 0, - "precision": "", - "read_only": 1 - }, - { - "fieldname": "total_expense_claim", - "fieldtype": "Currency", - "label": "Total Expense Claim", - "permlevel": 0, - "precision": "", - "read_only": 1 - }, { "fieldname": "column_break_22", "fieldtype": "Column Break", "permlevel": 0, "precision": "" }, - { - "fieldname": "est_material_cost", - "fieldtype": "Currency", - "label": "Estimated Material Cost", - "no_copy": 0, - "oldfieldname": "est_material_cost", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "search_index": 0 - }, - { - "fieldname": "total_billing_amount", - "fieldtype": "Currency", - "label": "Total Billing Amount", - "permlevel": 0, - "precision": "", - "read_only": 1 - }, { "fieldname": "cost_center", "fieldtype": "Link", @@ -271,24 +263,30 @@ "permlevel": 0 }, { - "fieldname": "margin", + "fieldname": "project_details", "fieldtype": "Section Break", - "label": "Margin", - "oldfieldtype": "Column Break", - "permlevel": 0, - "width": "50%" + "label": "", + "oldfieldtype": "Section Break", + "options": "icon-money", + "permlevel": 0 }, { - "fieldname": "gross_margin_value", + "description": "", + "fieldname": "total_costing_amount", "fieldtype": "Currency", - "label": "Gross Margin Value", - "no_copy": 0, - "oldfieldname": "gross_margin_value", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", + "label": "Total Costing Amount (via Time Logs)", "permlevel": 0, - "reqd": 0, - "search_index": 0 + "precision": "", + "read_only": 1 + }, + { + "description": "", + "fieldname": "total_expense_claim", + "fieldtype": "Currency", + "label": "Total Expense Claim (via Expense Claims)", + "permlevel": 0, + "precision": "", + "read_only": 1 }, { "fieldname": "column_break_28", @@ -297,44 +295,59 @@ "precision": "" }, { - "fieldname": "per_gross_margin", + "description": "", + "fieldname": "total_billing_amount", "fieldtype": "Currency", - "label": "Gross Margin %", + "label": "Total Billing Amount (via Time Logs)", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "margin", + "fieldtype": "Section Break", + "label": "", + "oldfieldtype": "Column Break", + "permlevel": 0, + "width": "50%" + }, + { + "fieldname": "gross_margin", + "fieldtype": "Currency", + "label": "Gross Margin", "no_copy": 0, - "oldfieldname": "per_gross_margin", + "oldfieldname": "gross_margin_value", "oldfieldtype": "Currency", "options": "Company:company:default_currency", "permlevel": 0, + "read_only": 1, "reqd": 0, "search_index": 0 }, { - "fieldname": "customer_details", - "fieldtype": "Section Break", - "label": "Customer Details", - "oldfieldtype": "Section Break", - "options": "icon-user", - "permlevel": 0 + "fieldname": "column_break_37", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" }, { - "fieldname": "customer", - "fieldtype": "Link", - "in_filter": 1, - "label": "Customer", + "fieldname": "per_gross_margin", + "fieldtype": "Percent", + "label": "Gross Margin %", "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", + "oldfieldname": "per_gross_margin", + "oldfieldtype": "Currency", + "options": "", "permlevel": 0, - "print_hide": 1, + "read_only": 1, "reqd": 0, - "search_index": 1 + "search_index": 0 } ], "icon": "icon-puzzle-piece", "idx": 1, "max_attachments": 4, - "modified": "2015-04-13 07:13:06.962942", + "modified": "2015-04-14 07:37:56.810833", "modified_by": "Administrator", "module": "Projects", "name": "Project", diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 0b45e4d3bb..d2741e2926 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -44,6 +44,8 @@ class Project(Document): def sync_tasks(self): """sync tasks and remove table""" + if self.flags.dont_sync_tasks: return + task_names = [] for t in self.tasks: if t.task_id: @@ -79,6 +81,22 @@ class Project(Document): project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0] frappe.db.set_value("Project", self.name, "percent_complete", int(float(completed) / total * 100)) + + def update_costing(self): + total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount, + sum(total_billing_amount) as billing_amount, sum(total_expense_claim) as expense_claim, + min(act_start_date) as start_date, max(act_end_date) as end_date, sum(actual_time) as time + from `tabTask` where project = %s""", self.name, as_dict=1)[0] + + self.total_costing_amount = total_cost.costing_amount + self.total_billing_amount = total_cost.billing_amount + self.total_expense_claim = total_cost.expense_claim + self.actual_start_date = total_cost.start_date + self.actual_end_date = total_cost.end_date + self.actual_time = total_cost.time + self.gross_margin = flt(total_cost.billing_amount) - flt(total_cost.costing_amount) + if self.total_billing_amount: + self.per_gross_margin = (self.gross_margin / flt(self.total_billing_amount)) *100 @frappe.whitelist() def get_cost_center_name(project_name): diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 664210016d..fc604c264c 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -18,24 +18,14 @@ "reqd": 1 }, { - "fieldname": "exp_start_date", - "fieldtype": "Date", - "label": "Expected Start Date", - "oldfieldname": "exp_start_date", - "oldfieldtype": "Date", - "permlevel": 0, - "reqd": 0 - }, - { - "fieldname": "exp_end_date", - "fieldtype": "Date", - "in_filter": 1, - "label": "Expected End Date", - "oldfieldname": "exp_end_date", - "oldfieldtype": "Date", - "permlevel": 0, - "reqd": 0, - "search_index": 1 + "fieldname": "project", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Project", + "oldfieldname": "project", + "oldfieldtype": "Link", + "options": "Project", + "permlevel": 0 }, { "fieldname": "column_break0", @@ -56,16 +46,6 @@ "options": "Open\nWorking\nPending Review\nClosed\nCancelled", "permlevel": 0 }, - { - "fieldname": "project", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Project", - "oldfieldname": "project", - "oldfieldtype": "Link", - "options": "Project", - "permlevel": 0 - }, { "fieldname": "priority", "fieldtype": "Select", @@ -104,33 +84,45 @@ "oldfieldtype": "Section Break", "permlevel": 0 }, + { + "fieldname": "exp_start_date", + "fieldtype": "Date", + "label": "Expected Start Date", + "oldfieldname": "exp_start_date", + "oldfieldtype": "Date", + "permlevel": 0, + "reqd": 0 + }, { "default": "0", - "description": "in Hours", + "description": "", "fieldname": "expected_time", "fieldtype": "Float", - "label": "Expected Time", + "label": "Expected Time (in hours)", "oldfieldname": "exp_total_hrs", "oldfieldtype": "Data", "permlevel": 0, "reqd": 0 }, { - "fieldname": "column_break_12", + "fieldname": "column_break_11", "fieldtype": "Column Break", "permlevel": 0, "precision": "" }, { - "fieldname": "allocated_budget", - "fieldtype": "Currency", - "label": "Allocated Budget", - "oldfieldname": "allocated_budget", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0 + "fieldname": "exp_end_date", + "fieldtype": "Date", + "in_filter": 1, + "label": "Expected End Date", + "oldfieldname": "exp_end_date", + "oldfieldtype": "Date", + "permlevel": 0, + "reqd": 0, + "search_index": 1 }, { + "description": "", "fieldname": "actual", "fieldtype": "Section Break", "label": "", @@ -142,7 +134,7 @@ { "fieldname": "act_start_date", "fieldtype": "Date", - "label": "Actual Start Date", + "label": "Actual Start Date (via Time Logs)", "oldfieldname": "act_start_date", "oldfieldtype": "Date", "permlevel": 0, @@ -150,10 +142,10 @@ }, { "default": "", - "description": "in Hours", + "description": "", "fieldname": "actual_time", "fieldtype": "Float", - "label": "Actual Time", + "label": "Actual Time (in hours)", "options": "", "permlevel": 0, "precision": "", @@ -168,7 +160,7 @@ { "fieldname": "act_end_date", "fieldtype": "Date", - "label": "Actual End Date", + "label": "Actual End Date (via Time Logs)", "oldfieldname": "act_end_date", "oldfieldtype": "Date", "permlevel": 0, @@ -183,7 +175,7 @@ { "fieldname": "total_costing_amount", "fieldtype": "Currency", - "label": "Total Costing Amount", + "label": "Total Costing Amount (via Time Logs)", "oldfieldname": "actual_budget", "oldfieldtype": "Currency", "options": "Company:company:default_currency", @@ -193,7 +185,7 @@ { "fieldname": "total_expense_claim", "fieldtype": "Currency", - "label": "Total Expense Claim", + "label": "Total Expense Claim (via Expense Claim)", "options": "Company:company:default_currency", "permlevel": 0, "precision": "", @@ -209,7 +201,7 @@ "fieldname": "total_billing_amount", "fieldtype": "Currency", "hidden": 0, - "label": "Total Billing Amount", + "label": "Total Billing Amount (via Time Logs)", "permlevel": 0, "precision": "", "read_only": 1 @@ -257,7 +249,7 @@ "idx": 1, "istable": 0, "max_attachments": 5, - "modified": "2015-04-13 05:36:33.544083", + "modified": "2015-04-14 07:56:24.481667", "modified_by": "Administrator", "module": "Projects", "name": "Task", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 8c74edb941..a03340f7de 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -64,18 +64,10 @@ class Task(Document): def update_project(self): if self.project and frappe.db.exists("Project", self.project): - total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount, - sum(total_billing_amount) as billing_amount, sum(total_expense_claim) as expense_claim, - min(act_start_date) as start_date, max(act_end_date) as end_date, sum(actual_time) as time - from `tabTask` where project = %s""", self.project, as_dict=1)[0] - frappe.db.set_values("Project", self.project, { - "total_costing_amount": total_cost.costing_amount, - "total_billing_amount": total_cost.billing_amount, - "total_expense_claim": total_cost.expense_claim, - "actual_start_date": total_cost.start_date, - "actual_end_date": total_cost.end_date, - "actual_time": total_cost.time - }) + project = frappe.get_doc("Project", self.project) + project.flags.dont_sync_tasks = True + project.update_costing() + project.save() @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index 5625ff3975..2f960ab997 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -4,7 +4,6 @@ frappe.provide("erpnext.projects"); frappe.ui.form.on("Time Log", "onload", function(frm) { - frm.set_query("task", erpnext.queries.task); if (frm.doc.for_manufacturing) { frappe.ui.form.trigger("Time Log", "production_order"); } @@ -88,3 +87,11 @@ frappe.ui.form.on("Time Log", "billable", function(frm) { frm.doc("billing_amount", 0); } }); + +cur_frm.fields_dict['task'].get_query = function(doc) { + return { + filters:{ + 'project': doc.project + } + } +} diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index 09c4e87c33..e9e2077225 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -232,10 +232,10 @@ }, { "default": "0", - "description": "per hour", + "description": "", "fieldname": "costing_rate", "fieldtype": "Currency", - "label": "Costing Rate", + "label": "Costing Rate (per hour)", "permlevel": 0, "precision": "", "read_only": 1 @@ -257,10 +257,10 @@ }, { "default": "0", - "description": "per hour", + "description": "", "fieldname": "billing_rate", "fieldtype": "Currency", - "label": "Billing Rate", + "label": "Billing Rate (per hour)", "permlevel": 0, "precision": "", "read_only": 1 @@ -322,15 +322,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2015-04-06 02:47:16.187046", -======= - "modified": "2015-04-09 08:29:34.464429", ->>>>>>> Fixes in time_log -======= - "modified": "2015-04-13 04:31:20.855439", ->>>>>>> task and project- billing amount and actual amount added + "modified": "2015-04-14 09:07:28.468792", "modified_by": "Administrator", "module": "Projects", "name": "Time Log", From d4b33d3c146b184e87d03a83e2d8d1c296b1056e Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 15 Apr 2015 16:41:39 +0530 Subject: [PATCH 17/27] Costing added in Time log Batch, Sales Order added to Project + more fixes --- .../hr/doctype/expense_claim/expense_claim.py | 8 +++--- erpnext/projects/doctype/project/project.js | 21 ++++++++++++++ .../doctype/time_log_batch/time_log_batch.js | 2 +- .../time_log_batch/time_log_batch.json | 28 +++++++++++++++++-- .../doctype/time_log_batch/time_log_batch.py | 15 ++++++---- .../time_log_batch_detail.json | 28 ++++++++++++------- 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index fb720b025e..b5fe880bd2 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import get_fullname +from frappe.utils import get_fullname, flt from frappe.model.document import Document from erpnext.hr.utils import set_employee_name from erpnext.accounts.utils import validate_fiscal_year @@ -39,8 +39,8 @@ class ExpenseClaim(Document): self.total_claimed_amount = 0 self.total_sanctioned_amount = 0 for d in self.expenses: - self.total_claimed_amount += d.claim_amount - self.total_sanctioned_amount += d.sanctioned_amount + self.total_claimed_amount += flt(d.claim_amount) + self.total_sanctioned_amount += flt(d.sanctioned_amount) def validate_exp_details(self): if not self.get('expenses'): @@ -62,5 +62,5 @@ class ExpenseClaim(Document): def validate_sanctioned_amount(self): for d in self.expenses: - if d.sanctioned_amount > d.claim_amount: + if flt(d.sanctioned_amount) > flt(d.claim_amount): frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx)) \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 56cc09dbee..1148efeadc 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -1,6 +1,19 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +frappe.ui.form.on("Project", { + onload: function(frm) { + var so = frappe.meta.get_docfield("Project", "sales_order"); + so.get_route_options_for_new_doc = function(field) { + if(frm.is_new()) return; + return { + "customer": frm.doc.customer, + "project_name": frm.doc.name + } + } + } +}); + frappe.ui.form.on("Project Task", "edit_task", function(frm, doctype, name) { var doc = frappe.get_doc(doctype, name); if(doc.task_id) { @@ -37,3 +50,11 @@ cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) { query: "erpnext.controllers.queries.customer_query" } } + +cur_frm.fields_dict['sales_order'].get_query = function(doc) { + return { + filters:{ + 'project_name': doc.name + } + } +} diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch.js b/erpnext/projects/doctype/time_log_batch/time_log_batch.js index 0635db09b9..39141ed968 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch.js +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt cur_frm.add_fetch("time_log", "activity_type", "activity_type"); -cur_frm.add_fetch("time_log", "owner", "created_by"); +cur_frm.add_fetch("time_log", "billing_amount", "billing_amount"); cur_frm.add_fetch("time_log", "hours", "hours"); cur_frm.set_query("time_log", "time_logs", function(doc) { diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch.json b/erpnext/projects/doctype/time_log_batch/time_log_batch.json index a42c4ea437..c53b1e9622 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch.json +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch.json @@ -32,6 +32,7 @@ "fieldtype": "Select", "in_list_view": 0, "label": "Status", + "no_copy": 1, "options": "Draft\nSubmitted\nBilled\nCancelled", "permlevel": 0, "read_only": 1 @@ -60,7 +61,14 @@ "reqd": 1 }, { - "description": "In Hours", + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "default": "0", + "description": "updated via Time Logs", "fieldname": "total_hours", "fieldtype": "Float", "in_list_view": 1, @@ -68,6 +76,22 @@ "permlevel": 0, "read_only": 1 }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "default": "0", + "description": "updated via Time Logs", + "fieldname": "total_billing_amount", + "fieldtype": "Float", + "label": "Total Billing Amount", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -83,7 +107,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, - "modified": "2015-02-05 05:11:48.360822", + "modified": "2015-04-15 06:07:41.771962", "modified_by": "Administrator", "module": "Projects", "name": "Time Log Batch", diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch.py b/erpnext/projects/doctype/time_log_batch/time_log_batch.py index 95655363c6..9ea3256bcf 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch.py +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc @@ -14,12 +15,12 @@ class TimeLogBatch(Document): def validate(self): self.set_status() - self.total_hours = 0.0 for d in self.get("time_logs"): tl = frappe.get_doc("Time Log", d.time_log) self.update_time_log_values(d, tl) self.validate_time_log_is_submitted(tl) - self.total_hours += float(tl.hours or 0.0) + self.total_hours += flt(tl.hours) + self.total_billing_amount += flt(tl.billing_amount) def update_time_log_values(self, d, tl): d.update({ @@ -29,7 +30,9 @@ class TimeLogBatch(Document): }) def validate_time_log_is_submitted(self, tl): - if tl.status != "Submitted" and self.docstatus == 0: + if tl.status == "Batched for Billing": + frappe.throw(_("Time Log {0} already billed").format(tl.name)) + elif tl.status != "Submitted": frappe.throw(_("Time Log {0} must be 'Submitted'").format(tl.name)) def set_status(self): @@ -65,15 +68,15 @@ def make_sales_invoice(source_name, target=None): def update_item(source_doc, target_doc, source_parent): target_doc.stock_uom = "Hour" target_doc.description = "via Time Logs" + target_doc.qty = 1 target = frappe.new_doc("Sales Invoice") target.append("items", get_mapped_doc("Time Log Batch", source_name, { "Time Log Batch": { "doctype": "Sales Invoice Item", "field_map": { - "rate": "base_rate", - "name": "time_log_batch", - "total_hours": "qty", + "total_billing_amount": "rate", + "name": "time_log_batch" }, "postprocess": update_item } diff --git a/erpnext/projects/doctype/time_log_batch_detail/time_log_batch_detail.json b/erpnext/projects/doctype/time_log_batch_detail/time_log_batch_detail.json index 15e7e8e29b..da8ff4cd4a 100644 --- a/erpnext/projects/doctype/time_log_batch_detail/time_log_batch_detail.json +++ b/erpnext/projects/doctype/time_log_batch_detail/time_log_batch_detail.json @@ -1,5 +1,5 @@ { - "creation": "2013-03-05 09:11:06.000000", + "creation": "2013-03-05 09:11:06", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -15,14 +15,19 @@ "width": "200px" }, { - "fieldname": "created_by", - "fieldtype": "Link", + "fieldname": "hours", + "fieldtype": "Float", "in_list_view": 1, - "label": "Created By", - "options": "User", + "label": "Hours", "permlevel": 0, "read_only": 1 }, + { + "fieldname": "section_break_3", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, { "fieldname": "activity_type", "fieldtype": "Data", @@ -32,18 +37,21 @@ "read_only": 1 }, { - "fieldname": "hours", + "fieldname": "billing_amount", "fieldtype": "Float", "in_list_view": 1, - "label": "Hours", - "permlevel": 0 + "label": "Billing Amount", + "permlevel": 0, + "precision": "", + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2013-12-20 19:21:54.000000", + "modified": "2015-04-15 05:35:08.805589", "modified_by": "Administrator", "module": "Projects", "name": "Time Log Batch Detail", - "owner": "Administrator" + "owner": "Administrator", + "permissions": [] } \ No newline at end of file From 4cffbe26dc852e8c102e5f9e9a72068404b91dc6 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 15 Apr 2015 17:33:26 +0530 Subject: [PATCH 18/27] rate removed from time log batch --- .../time_log_batch/time_log_batch.json | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch.json b/erpnext/projects/doctype/time_log_batch/time_log_batch.json index c53b1e9622..93726ee7ec 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch.json +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch.json @@ -14,18 +14,6 @@ "permlevel": 0, "reqd": 1 }, - { - "description": "For Sales Invoice", - "fieldname": "rate", - "fieldtype": "Currency", - "label": "Rate", - "permlevel": 0 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "permlevel": 0 - }, { "default": "Draft", "fieldname": "status", @@ -37,6 +25,11 @@ "permlevel": 0, "read_only": 1 }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "permlevel": 0 + }, { "description": "Will be updated after Sales Invoice is Submitted.", "fieldname": "sales_invoice", @@ -107,7 +100,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, - "modified": "2015-04-15 06:07:41.771962", + "modified": "2015-04-15 08:00:52.746961", "modified_by": "Administrator", "module": "Projects", "name": "Time Log Batch", From e7ddb72e7f666115fcaf15da7854f806984a1db7 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 15 Apr 2015 18:21:53 +0530 Subject: [PATCH 19/27] patch for renaming project feilds --- erpnext/patches.txt | 1 + erpnext/patches/v5_0/project_costing.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 erpnext/patches/v5_0/project_costing.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0e0fda9311..0665df4ab1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -143,3 +143,4 @@ execute:frappe.permissions.reset_perms("Expense Claim Type") #2014-06-19 erpnext.patches.v5_0.execute_on_doctype_update erpnext.patches.v4_2.fix_recurring_orders erpnext.patches.v4_2.delete_gl_entries_for_cancelled_invoices +erpnext.patches.v5_0.project_costing diff --git a/erpnext/patches/v5_0/project_costing.py b/erpnext/patches/v5_0/project_costing.py new file mode 100644 index 0000000000..ffeb396a66 --- /dev/null +++ b/erpnext/patches/v5_0/project_costing.py @@ -0,0 +1,6 @@ +import frappe + +def execute(): + frappe.db.sql("update `tabProject` set expected_start_date = project_start_date, \ + expected_end_date = completion_date, actual_end_date = act_completion_date, \ + estimated_costing = project_value, gross_margin = gross_margin_value") \ No newline at end of file From 50a0fd6204efbd136a4aea86098a8a588ef76277 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 16 Apr 2015 10:15:04 +0530 Subject: [PATCH 20/27] feild rename fixes in Project --- erpnext/projects/doctype/project/project.js | 2 +- erpnext/projects/doctype/project/project.py | 8 -------- erpnext/projects/doctype/project/project_list.js | 2 +- .../project_wise_stock_tracking.py | 8 ++++---- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 1148efeadc..aa55876654 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -27,7 +27,7 @@ frappe.ui.form.on("Project Task", "edit_task", function(frm, doctype, name) { cur_frm.cscript.refresh = function(doc) { if(!doc.__islocal) { cur_frm.add_custom_button(__("Gantt Chart"), function() { - frappe.route_options = {"project": doc.name, "start": doc.project_start_date, "end": doc.completion_date}; + frappe.route_options = {"project": doc.name, "start": doc.expected_start_date, "end": doc.expected_end_date}; frappe.set_route("Gantt", "Task"); }, "icon-tasks", true); cur_frm.add_custom_button(__("Tasks"), function() { diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index d2741e2926..89cf523c1e 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -25,14 +25,6 @@ class Project(Document): "task_id": task.name }) - def get_gross_profit(self): - pft, per_pft =0, 0 - pft = flt(self.project_value) - flt(self.est_material_cost) - #if pft > 0: - per_pft = (flt(pft) / flt(self.project_value)) * 100 - ret = {'gross_margin_value': pft, 'per_gross_margin': per_pft} - return ret - def validate(self): self.validate_dates() self.sync_tasks() diff --git a/erpnext/projects/doctype/project/project_list.js b/erpnext/projects/doctype/project/project_list.js index 8281c7dbb9..b0d1ae8673 100644 --- a/erpnext/projects/doctype/project/project_list.js +++ b/erpnext/projects/doctype/project/project_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['Project'] = { - add_fields: ["status", "priority", "is_active", "percent_complete", "completion_date"], + add_fields: ["status", "priority", "is_active", "percent_complete", "expected_end_date"], filters:[["status","=", "Open"]], get_indicator: function(doc) { if(doc.status=="Open" && doc.percent_complete) { diff --git a/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py b/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py index 1ef760eac4..58001568b0 100644 --- a/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py +++ b/erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py @@ -17,8 +17,8 @@ def execute(filters=None): data.append([project.name, pr_item_map.get(project.name, 0), se_item_map.get(project.name, 0), dn_item_map.get(project.name, 0), project.project_name, project.status, project.company, - project.customer, project.project_value, project.project_start_date, - project.completion_date]) + project.customer, project.estimated_costing, project.expected_start_date, + project.expected_end_date]) return columns, data @@ -30,8 +30,8 @@ def get_columns(): _("Project Start Date") + ":Date:120", _("Completion Date") + ":Date:120"] def get_project_details(): - return frappe.db.sql(""" select name, project_name, status, company, customer, project_value, - project_start_date, completion_date from tabProject where docstatus < 2""", as_dict=1) + return frappe.db.sql(""" select name, project_name, status, company, customer, estimated_costing, + expected_start_date, expected_end_date from tabProject where docstatus < 2""", as_dict=1) def get_purchased_items_cost(): pr_items = frappe.db.sql("""select project_name, sum(base_net_amount) as amount From da8c6f6ae2333f91fac3944483a414ae0f9bcfb4 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 16 Apr 2015 11:25:34 +0530 Subject: [PATCH 21/27] test case fix for Time Log --- .../doctype/time_log/test_time_log.py | 11 +++++++++ erpnext/projects/doctype/time_log/time_log.py | 23 ++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py index 019d59694f..3d9e0be5f9 100644 --- a/erpnext/projects/doctype/time_log/test_time_log.py +++ b/erpnext/projects/doctype/time_log/test_time_log.py @@ -88,6 +88,17 @@ class TestTimeLog(unittest.TestCase): def test_total_activity_cost_for_project(self): frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """) frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """) + frappe.db.sql("""delete from `tabActivity Cost` where employee = "_T-Employee-0001" and activity_type = "_Test Activity Type" """) + + activity_cost = frappe.new_doc('Activity Cost') + activity_cost.update({ + "employee": "_T-Employee-0001", + "employee_name": "_Test Employee", + "activity_type": "_Test Activity Type", + "billing_rate": 100, + "costing_rate": 50 + }) + activity_cost.insert() frappe.get_doc({ "project_name": "_Test Project 1", diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 2018425055..1ee825f6eb 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -28,16 +28,12 @@ class TimeLog(Document): self.validate_cost() def on_submit(self): - if self.for_manufacturing: - self.update_production_order() - if self.task: - self.update_task() + self.update_production_order() + self.update_task() def on_cancel(self): - if self.for_manufacturing: - self.update_production_order() - if self.task: - self.update_task() + self.update_production_order() + self.update_task() def before_update_after_submit(self): self.set_status() @@ -132,8 +128,8 @@ class TimeLog(Document): def update_production_order(self): """Updates `start_date`, `end_date`, `status` for operation in Production Order.""" - - if self.production_order: + + if self.production_order and self.for_manufacturing: if not self.operation_id: frappe.throw(_("Operation ID not set")) @@ -231,9 +227,10 @@ class TimeLog(Document): frappe.throw(_("Task is Mandatory if Time Log is against a project")) def update_task(self): - task = frappe.get_doc("Task", self.task) - task.update_time_and_costing() - task.save() + if self.task and frappe.db.exists("Task", self.task): + task = frappe.get_doc("Task", self.task) + task.update_time_and_costing() + task.save() @frappe.whitelist() def get_events(start, end, filters=None): From e1951306eb2c4cd387eeb3ba4d62376c13da64be Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 17 Apr 2015 16:04:57 +0530 Subject: [PATCH 22/27] fix in time log batch list --- erpnext/projects/doctype/time_log_batch/test_time_log_batch.py | 1 - erpnext/projects/doctype/time_log_batch/time_log_batch_list.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py b/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py index 240ca97926..faa0a601e3 100644 --- a/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py +++ b/erpnext/projects/doctype/time_log_batch/test_time_log_batch.py @@ -44,7 +44,6 @@ def create_time_log(): def create_time_log_batch(time_log): tlb = frappe.get_doc({ "doctype": "Time Log Batch", - "rate": "500", "time_logs": [ { "doctype": "Time Log Batch Detail", diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch_list.js b/erpnext/projects/doctype/time_log_batch/time_log_batch_list.js index f4876b8b26..9c02ac38a9 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch_list.js +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['Time Log Batch'] = { - add_fields: ["status", "total_hours", "rate"], + add_fields: ["status", "total_hours"], get_indicator: function(doc) { return [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status]; } From 086c0463be75cd65ed5c4d5a8bb6d8fc75670800 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 20 Apr 2015 11:47:22 +0530 Subject: [PATCH 23/27] Fixes in Patch --- erpnext/patches/v5_0/project_costing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v5_0/project_costing.py b/erpnext/patches/v5_0/project_costing.py index ffeb396a66..33bb9c1630 100644 --- a/erpnext/patches/v5_0/project_costing.py +++ b/erpnext/patches/v5_0/project_costing.py @@ -1,6 +1,7 @@ import frappe def execute(): + frappe.reload_doctype("Project") frappe.db.sql("update `tabProject` set expected_start_date = project_start_date, \ expected_end_date = completion_date, actual_end_date = act_completion_date, \ estimated_costing = project_value, gross_margin = gross_margin_value") \ No newline at end of file From 832dfe7980b3f1a47c4227897758206ab5b8e4ac Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 21 Apr 2015 15:05:37 +0530 Subject: [PATCH 24/27] fixes in Time Log Batch --- erpnext/projects/doctype/time_log/time_log_list.js | 4 ++-- .../doctype/time_log_batch/time_log_batch.js | 12 ++++++++++++ .../doctype/time_log_batch/time_log_batch.py | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/time_log/time_log_list.js b/erpnext/projects/doctype/time_log/time_log_list.js index d4448068fa..a2eb05cfc9 100644 --- a/erpnext/projects/doctype/time_log/time_log_list.js +++ b/erpnext/projects/doctype/time_log/time_log_list.js @@ -3,7 +3,7 @@ // render frappe.listview_settings['Time Log'] = { - add_fields: ["status", "billable", "activity_type", "task", "project", "hours", "for_manufacturing"], + add_fields: ["status", "billable", "activity_type", "task", "project", "hours", "for_manufacturing", "billing_amount"], selectable: true, onload: function(me) { me.page.add_menu_item(__("Make Time Log Batch"), function() { @@ -37,7 +37,7 @@ frappe.listview_settings['Time Log'] = { $.extend(detail, { "time_log": d.name, "activity_type": d.activity_type, - "created_by": d.owner, + "billing_amount": d.billing_amount, "hours": d.hours }); }) diff --git a/erpnext/projects/doctype/time_log_batch/time_log_batch.js b/erpnext/projects/doctype/time_log_batch/time_log_batch.js index 39141ed968..6b5f08094d 100644 --- a/erpnext/projects/doctype/time_log_batch/time_log_batch.js +++ b/erpnext/projects/doctype/time_log_batch/time_log_batch.js @@ -36,3 +36,15 @@ $.extend(cur_frm.cscript, { }); } }); + +frappe.ui.form.on("Time Log Batch Detail", "time_log", function(frm, cdt, cdn) { + var tl = frm.doc.time_logs || []; + total_hr = 0; + total_amt = 0; + for(var i=0; i Date: Tue, 21 Apr 2015 19:03:08 +0530 Subject: [PATCH 25/27] fixes in Expense Claim and get_avtivity_cost function --- .../doctype/expense_claim/expense_claim.json | 5 ++- .../hr/doctype/expense_claim/expense_claim.py | 11 ++--- .../expense_claim/test_expense_claim.py | 3 -- erpnext/projects/doctype/time_log/time_log.js | 42 ++++++++++--------- erpnext/projects/doctype/time_log/time_log.py | 3 +- 5 files changed, 29 insertions(+), 35 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 95b8a81e6e..f82fbcc357 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -93,7 +93,8 @@ "oldfieldname": "expense_voucher_details", "oldfieldtype": "Table", "options": "Expense Claim Detail", - "permlevel": 0 + "permlevel": 0, + "reqd": 1 }, { "fieldname": "sb1", @@ -235,7 +236,7 @@ "icon": "icon-money", "idx": 1, "is_submittable": 1, - "modified": "2015-04-14 05:08:06.541441", + "modified": "2015-04-21 09:32:00.971151", "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 b5fe880bd2..6687399f37 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -18,11 +18,10 @@ class ExpenseClaim(Document): def validate(self): validate_fiscal_year(self.posting_date, self.fiscal_year, _("Posting Date"), self) - self.validate_exp_details() - self.calculate_total_amount() self.validate_sanctioned_amount() self.validate_expense_approver() self.validate_task() + self.calculate_total_amount() set_employee_name(self) def on_submit(self): @@ -38,14 +37,10 @@ class ExpenseClaim(Document): def calculate_total_amount(self): self.total_claimed_amount = 0 self.total_sanctioned_amount = 0 - for d in self.expenses: + for d in self.get('expenses'): self.total_claimed_amount += flt(d.claim_amount) self.total_sanctioned_amount += flt(d.sanctioned_amount) - def validate_exp_details(self): - if not self.get('expenses'): - frappe.throw(_("Please add expense voucher details")) - def validate_expense_approver(self): if self.exp_approver and "Expense Approver" not in frappe.get_roles(self.exp_approver): frappe.throw(_("{0} ({1}) must have role 'Expense Approver'")\ @@ -61,6 +56,6 @@ class ExpenseClaim(Document): frappe.throw(_("Task is mandatory if Expense Claim is against a Project")) def validate_sanctioned_amount(self): - for d in self.expenses: + for d in self.get('expenses'): if flt(d.sanctioned_amount) > flt(d.claim_amount): frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx)) \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index f5ae93ca68..a9091fba6a 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -23,8 +23,6 @@ class TestExpenseClaim(unittest.TestCase): expense_claim = frappe.get_doc({ "doctype": "Expense Claim", "employee": "_T-Employee-0001", - "posting_date": "2015-07-07", - "fiscal_year": "_Test Fiscal Year 2015", "approval_status": "Approved", "project": "_Test Project 1", "task": task_name, @@ -39,7 +37,6 @@ class TestExpenseClaim(unittest.TestCase): expense_claim2 = frappe.get_doc({ "doctype": "Expense Claim", "employee": "_T-Employee-0001", - "posting_date": "2015-07-07", "approval_status": "Approved", "project": "_Test Project 1", "task": task_name, diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js index 2f960ab997..ba933018ef 100644 --- a/erpnext/projects/doctype/time_log/time_log.js +++ b/erpnext/projects/doctype/time_log/time_log.js @@ -43,32 +43,34 @@ frappe.ui.form.on("Time Log", "to_time", function(frm) { }); -var calculate_cost = function(doc) { - cur_frm.set_value("costing_amount", doc.costing_rate * doc.hours); - if (doc.billable==1){ - cur_frm.set_value("billing_amount", doc.billing_rate * doc.hours); +var calculate_cost = function(frm) { + frm.set_value("costing_amount", frm.doc.costing_rate * frm.doc.hours); + if (frm.doc.billable==1){ + frm.set_value("billing_amount", frm.doc.billing_rate * frm.doc.hours); } } var get_activity_cost = function(frm) { - return frappe.call({ - method: "erpnext.projects.doctype.time_log.time_log.get_activity_cost", - args: { - "employee": frm.doc.employee, - "activity_type": frm.doc.activity_type - }, - callback: function(r) { - if(!r.exc) { - cur_frm.set_value("costing_rate", r.message.costing_rate); - cur_frm.set_value("billing_rate", r.message.billing_rate); - calculate_cost(frm.doc); + if (frm.doc.employee && frm.doc.activity_type){ + return frappe.call({ + method: "erpnext.projects.doctype.time_log.time_log.get_activity_cost", + args: { + "employee": frm.doc.employee, + "activity_type": frm.doc.activity_type + }, + callback: function(r) { + if(!r.exc && r.message) { + frm.set_value("costing_rate", r.message.costing_rate); + frm.set_value("billing_rate", r.message.billing_rate); + calculate_cost(frm); + } } - } - }); + }); + } } frappe.ui.form.on("Time Log", "hours", function(frm) { - calculate_cost(frm.doc); + calculate_cost(frm); }); frappe.ui.form.on("Time Log", "activity_type", function(frm) { @@ -81,10 +83,10 @@ frappe.ui.form.on("Time Log", "employee", function(frm) { frappe.ui.form.on("Time Log", "billable", function(frm) { if (frm.doc.billable==1) { - calculate_cost(frm.doc); + calculate_cost(frm); } else { - frm.doc("billing_amount", 0); + frm.set_value("billing_amount", 0); } }); diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 1ee825f6eb..25383a0b09 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -273,5 +273,4 @@ def get_events(start, end, filters=None): def get_activity_cost(employee=None, activity_type=None): rate = frappe.db.sql("""select costing_rate, billing_rate from `tabActivity Cost` where employee= %s and activity_type= %s""", (employee, activity_type), as_dict=1) - if rate: - return {"costing_rate": rate[0].costing_rate, "billing_rate": rate[0].billing_rate } + return rate[0] if rate else {} From 051d0ab79a17464db3f642676393bdc20cc29213 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 21 Apr 2015 19:04:36 +0530 Subject: [PATCH 26/27] function name validate cost changed to update cost in Time Log --- erpnext/projects/doctype/time_log/time_log.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/time_log/time_log.py b/erpnext/projects/doctype/time_log/time_log.py index 25383a0b09..f7b501d451 100644 --- a/erpnext/projects/doctype/time_log/time_log.py +++ b/erpnext/projects/doctype/time_log/time_log.py @@ -25,7 +25,7 @@ class TimeLog(Document): self.validate_production_order() self.validate_manufacturing() self.validate_task() - self.validate_cost() + self.update_cost() def on_submit(self): self.update_production_order() @@ -211,7 +211,7 @@ class TimeLog(Document): self.operation = None self.quantity = None - def validate_cost(self): + def update_cost(self): rate = get_activity_cost(self.employee, self.activity_type) if rate: self.costing_rate = rate.get('costing_rate') From ecb5d11e2d953a2d6dc167a3e3162a47bb5bd693 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 22 Apr 2015 11:25:29 +0530 Subject: [PATCH 27/27] Today added as default date for posting date in expense claim --- erpnext/hr/doctype/expense_claim/expense_claim.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index f82fbcc357..e08856a3bb 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -103,6 +103,7 @@ "permlevel": 0 }, { + "default": "Today", "fieldname": "posting_date", "fieldtype": "Date", "in_filter": 1, @@ -236,7 +237,7 @@ "icon": "icon-money", "idx": 1, "is_submittable": 1, - "modified": "2015-04-21 09:32:00.971151", + "modified": "2015-04-22 01:51:24.782515", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim",