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:
parent
d06b7049c7
commit
d18423d9c7
@ -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 |
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
23
erpnext/projects/doctype/project/test_project.js
Normal file
23
erpnext/projects/doctype/project/test_project.js
Normal 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()
|
||||||
|
]);
|
||||||
|
|
||||||
|
});
|
||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user