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_current_month_sales()
|
||||
self.update_project()
|
||||
|
||||
def validate_pos_paid_amount(self):
|
||||
if len(self.payments) == 0 and self.is_pos:
|
||||
@ -181,6 +182,7 @@ class SalesInvoice(SellingController):
|
||||
frappe.db.set(self, 'status', 'Cancelled')
|
||||
|
||||
self.update_current_month_sales()
|
||||
self.update_project()
|
||||
|
||||
def update_current_month_sales(self):
|
||||
if frappe.flags.in_test:
|
||||
@ -912,6 +914,13 @@ class SalesInvoice(SellingController):
|
||||
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):
|
||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||
list_context = get_list_context(context)
|
||||
@ -991,4 +1000,4 @@ def make_sales_return(source_name, target_doc=None):
|
||||
def set_account_for_mode_of_payment(self):
|
||||
for data in self.payments:
|
||||
if not data.account:
|
||||
data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
|
||||
data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
|
||||
|
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
|
||||
|
||||
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">
|
||||
|
||||
* 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
|
||||
|
||||
|
@ -893,7 +893,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Costing Amount (via Time Logs)",
|
||||
"label": "Total Costing Amount (via Timesheets)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -939,6 +939,36 @@
|
||||
"set_only_once": 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_on_submit": 0,
|
||||
@ -969,36 +999,6 @@
|
||||
"set_only_once": 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_on_submit": 0,
|
||||
@ -1028,6 +1028,36 @@
|
||||
"set_only_once": 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_on_submit": 0,
|
||||
@ -1035,7 +1065,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "",
|
||||
"fieldname": "total_billing_amount",
|
||||
"fieldname": "total_billable_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -1044,7 +1074,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Billing Amount (via Time Logs)",
|
||||
"label": "Total Billable Amount (via Timesheets)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -1065,7 +1095,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "total_purchase_cost",
|
||||
"fieldname": "total_billed_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -1074,7 +1104,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Purchase Cost (via Purchase Invoice)",
|
||||
"label": "Total Billed Amount (via Sales Invoices)",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -1095,8 +1125,8 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "total_sales_cost",
|
||||
"fieldtype": "Currency",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@ -1104,14 +1134,14 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Sales Cost (via Sales Order)",
|
||||
"label": "Default Cost Center",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cost Center",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -1255,7 +1285,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 4,
|
||||
"modified": "2017-07-26 14:36:20.857673",
|
||||
"modified": "2017-12-10 08:40:46.843201",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"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
|
||||
|
||||
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
|
||||
order by total_hours desc''', self.name, as_dict=True))
|
||||
|
||||
self.update_costing()
|
||||
|
||||
def __setup__(self):
|
||||
self.onload()
|
||||
|
||||
@ -68,7 +70,7 @@ class Project(Document):
|
||||
if self.expected_start_date and self.expected_end_date:
|
||||
if getdate(self.expected_end_date) < getdate(self.expected_start_date):
|
||||
frappe.throw(_("Expected End Date can not be less than Expected Start Date"))
|
||||
|
||||
|
||||
def validate_weights(self):
|
||||
sum = 0
|
||||
for task in self.tasks:
|
||||
@ -174,28 +176,37 @@ class Project(Document):
|
||||
self.actual_end_date = from_time_sheet.end_date
|
||||
|
||||
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.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:
|
||||
self.per_gross_margin = (self.gross_margin / flt(self.total_billing_amount)) *100
|
||||
if self.total_billed_amount:
|
||||
self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) *100
|
||||
|
||||
def update_purchase_costing(self):
|
||||
total_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
|
||||
from `tabPurchase Invoice Item` where project = %s and docstatus=1""", self.name)
|
||||
|
||||
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0
|
||||
|
||||
def update_sales_costing(self):
|
||||
total_sales_cost = frappe.db.sql("""select sum(base_grand_total)
|
||||
|
||||
def update_sales_amount(self):
|
||||
total_sales_amount = frappe.db.sql("""select sum(base_grand_total)
|
||||
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):
|
||||
url = get_url("/project/?name={0}".format(self.name))
|
||||
@ -219,7 +230,7 @@ class Project(Document):
|
||||
self.load_tasks()
|
||||
self.sync_tasks()
|
||||
self.update_dependencies_on_duplicated_project()
|
||||
|
||||
|
||||
def update_dependencies_on_duplicated_project(self):
|
||||
if self.flags.dont_sync_tasks: return
|
||||
if not self.copied_from:
|
||||
@ -289,10 +300,10 @@ def get_list_context(context=None):
|
||||
|
||||
def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
|
||||
return frappe.db.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)
|
||||
from `tabUser`
|
||||
where enabled=1
|
||||
and name not in ("Guest", "Administrator")
|
||||
and name not in ("Guest", "Administrator")
|
||||
and ({key} like %(txt)s
|
||||
or full_name like %(txt)s)
|
||||
{fcond} {mcond}
|
||||
|
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()
|
||||
]);
|
||||
|
||||
});
|
@ -110,7 +110,7 @@ class SalesOrder(SellingController):
|
||||
for d in self.get("items"):
|
||||
if not d.delivery_date:
|
||||
d.delivery_date = self.delivery_date
|
||||
|
||||
|
||||
if getdate(self.transaction_date) > getdate(d.delivery_date):
|
||||
frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"),
|
||||
indicator='orange', title=_('Warning'))
|
||||
@ -191,7 +191,7 @@ class SalesOrder(SellingController):
|
||||
if self.project:
|
||||
project = frappe.get_doc("Project", self.project)
|
||||
project.flags.dont_sync_tasks = True
|
||||
project.update_sales_costing()
|
||||
project.update_sales_amount()
|
||||
project.save()
|
||||
project_list.append(self.project)
|
||||
|
||||
@ -492,7 +492,7 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
target.ignore_pricing_rule = 1
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
|
||||
|
||||
# set company address
|
||||
target.update(get_company_address(target.company))
|
||||
if target.company_address:
|
||||
@ -820,4 +820,4 @@ def get_default_bom_item(item_code):
|
||||
order_by='is_default desc')
|
||||
bom = bom[0].name if bom else None
|
||||
|
||||
return bom
|
||||
return bom
|
||||
|
Loading…
x
Reference in New Issue
Block a user