Project Margin Calculation Improvement (#11911)

* Project Margin Calculation Improvement

* Documentation modification

* Change Total Planned Sales to Total Sales Amount

* Change documentation screenshot
This commit is contained in:
Charles-Henri Decultot 2017-12-12 10:29:59 +01:00 committed by Nabin Hait
parent d06b7049c7
commit d18423d9c7
7 changed files with 136 additions and 63 deletions

View File

@ -143,6 +143,7 @@ class SalesInvoice(SellingController):
self.update_time_sheet(self.name) self.update_time_sheet(self.name)
self.update_current_month_sales() self.update_current_month_sales()
self.update_project()
def validate_pos_paid_amount(self): def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos: if len(self.payments) == 0 and self.is_pos:
@ -181,6 +182,7 @@ class SalesInvoice(SellingController):
frappe.db.set(self, 'status', 'Cancelled') frappe.db.set(self, 'status', 'Cancelled')
self.update_current_month_sales() self.update_current_month_sales()
self.update_project()
def update_current_month_sales(self): def update_current_month_sales(self):
if frappe.flags.in_test: if frappe.flags.in_test:
@ -912,6 +914,13 @@ class SalesInvoice(SellingController):
serial_no, sales_invoice serial_no, sales_invoice
))) )))
def update_project(self):
if self.project:
project = frappe.get_doc("Project", self.project)
project.flags.dont_sync_tasks = True
project.update_billed_amount()
project.save()
def get_list_context(context=None): def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context) list_context = get_list_context(context)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -81,13 +81,13 @@ You can make a [Cost Center](/docs/user/manual/en/accounts/setup/cost-center.htm
###Project Costing ###Project Costing
The Project Costing section helps you track the time and expenses incurred against the project. The Project Costing section helps you track the time, expenses and purchases incurred against the project.
<img class="screenshot" alt="Project - Costing" src="/docs/assets/img/project/project_costing.png"> <img class="screenshot" alt="Project - Costing" src="/docs/assets/img/project/project_costing.png">
* The Costing Section is updated based on Time Logs made. * The Total Cost is composed of the costing amount from timesheets, the total cost from expense claims and the total cost from purchase invoices created against this project.
* Gross Margin is the difference between Total Costing Amount and Total Billing Amount * The Gross Margin is the difference between Total Billed Amount and the Total Cost Amount for this project.
###Billing ###Billing

View File

@ -893,7 +893,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Total Costing Amount (via Time Logs)", "label": "Total Costing Amount (via Timesheets)",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -939,6 +939,36 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_purchase_cost",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Purchase Cost (via Purchase Invoice)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -969,36 +999,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cost_center",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Cost Center",
"length": 0,
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1028,6 +1028,36 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_sales_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Sales Amount (via Sales Order)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1035,7 +1065,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "", "description": "",
"fieldname": "total_billing_amount", "fieldname": "total_billable_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1044,7 +1074,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Total Billing Amount (via Time Logs)", "label": "Total Billable Amount (via Timesheets)",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -1065,7 +1095,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "total_purchase_cost", "fieldname": "total_billed_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1074,7 +1104,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Total Purchase Cost (via Purchase Invoice)", "label": "Total Billed Amount (via Sales Invoices)",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -1095,8 +1125,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "total_sales_cost", "fieldname": "cost_center",
"fieldtype": "Currency", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -1104,14 +1134,14 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Total Sales Cost (via Sales Order)", "label": "Default Cost Center",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Cost Center",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 1, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
@ -1255,7 +1285,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 4, "max_attachments": 4,
"modified": "2017-07-26 14:36:20.857673", "modified": "2017-12-10 08:40:46.843201",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Project", "name": "Project",

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
@ -25,6 +25,8 @@ class Project(Document):
from `tabTimesheet Detail` where project=%s and docstatus < 2 group by activity_type from `tabTimesheet Detail` where project=%s and docstatus < 2 group by activity_type
order by total_hours desc''', self.name, as_dict=True)) order by total_hours desc''', self.name, as_dict=True))
self.update_costing()
def __setup__(self): def __setup__(self):
self.onload() self.onload()
@ -174,15 +176,18 @@ class Project(Document):
self.actual_end_date = from_time_sheet.end_date self.actual_end_date = from_time_sheet.end_date
self.total_costing_amount = from_time_sheet.costing_amount self.total_costing_amount = from_time_sheet.costing_amount
self.total_billing_amount = from_time_sheet.billing_amount self.total_billable_amount = from_time_sheet.billing_amount
self.actual_time = from_time_sheet.time self.actual_time = from_time_sheet.time
self.total_expense_claim = from_expense_claim.total_sanctioned_amount self.total_expense_claim = from_expense_claim.total_sanctioned_amount
self.update_purchase_costing()
self.update_sales_amount()
self.update_billed_amount()
self.gross_margin = flt(self.total_billing_amount) - flt(self.total_costing_amount) self.gross_margin = flt(self.total_billed_amount) - (flt(self.total_costing_amount) + flt(self.total_expense_claim) + flt(self.total_purchase_cost))
if self.total_billing_amount: if self.total_billed_amount:
self.per_gross_margin = (self.gross_margin / flt(self.total_billing_amount)) *100 self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) *100
def update_purchase_costing(self): def update_purchase_costing(self):
total_purchase_cost = frappe.db.sql("""select sum(base_net_amount) total_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
@ -190,11 +195,17 @@ class Project(Document):
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0 self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0
def update_sales_costing(self): def update_sales_amount(self):
total_sales_cost = frappe.db.sql("""select sum(base_grand_total) total_sales_amount = frappe.db.sql("""select sum(base_grand_total)
from `tabSales Order` where project = %s and docstatus=1""", self.name) from `tabSales Order` where project = %s and docstatus=1""", self.name)
self.total_sales_cost = total_sales_cost and total_sales_cost[0][0] or 0 self.total_sales_amount = total_sales_amount and total_sales_amount[0][0] or 0
def update_billed_amount(self):
total_billed_amount = frappe.db.sql("""select sum(base_grand_total)
from `tabSales Invoice` where project = %s and docstatus=1""", self.name)
self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0
def send_welcome_email(self): def send_welcome_email(self):

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Project", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Project
() => frappe.tests.make('Project', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -191,7 +191,7 @@ class SalesOrder(SellingController):
if self.project: if self.project:
project = frappe.get_doc("Project", self.project) project = frappe.get_doc("Project", self.project)
project.flags.dont_sync_tasks = True project.flags.dont_sync_tasks = True
project.update_sales_costing() project.update_sales_amount()
project.save() project.save()
project_list.append(self.project) project_list.append(self.project)