Merge branch 'staging-fixes' into staging
diff --git a/README.md b/README.md
index 3b3be96..f4a08be 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,16 @@
<p align="center">
<p>ERP made simple</p>
</p>
-</div>
-[![Build Status](https://travis-ci.com/frappe/erpnext.png)](https://travis-ci.com/frappe/erpnext) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/frappe/erpnext?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
+[![Build Status](https://travis-ci.com/frappe/erpnext.png)](https://travis-ci.com/frappe/erpnext)
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/frappe/erpnext?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
+[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
[https://erpnext.com](https://erpnext.com)
+</div>
+
Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB.
ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 1af7e91..217d091 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '10.1.53'
+__version__ = '10.1.54'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index eef85de..635a8c2 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -12,6 +12,11 @@
class Account(NestedSet):
nsm_parent_field = 'parent_account'
+ def on_update(self):
+ if frappe.local.flags.ignore_on_update:
+ return
+ else:
+ super().on_update()
def onload(self):
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index f4cf6fa..9b812a8 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -6,6 +6,7 @@
from frappe.utils import cstr
from unidecode import unidecode
from six import iteritems
+from frappe.utils.nestedset import rebuild_tree
def create_charts(company, chart_template=None, existing_company=None):
chart = get_chart(chart_template, existing_company)
@@ -53,7 +54,12 @@
_import_accounts(child, account.name, root_type)
+ # Rebuild NestedSet HSM tree for Account Doctype
+ # after all accounts are already inserted.
+ frappe.local.flags.ignore_on_update = True
_import_accounts(chart, None, None, root_account=True)
+ rebuild_tree("Account", "parent_account")
+ frappe.local.flags.ignore_on_update = False
def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number:
diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py
index 4ec97c4..f2a5c16 100755
--- a/erpnext/accounts/doctype/sales_invoice/pos.py
+++ b/erpnext/accounts/doctype/sales_invoice/pos.py
@@ -572,7 +572,7 @@
frappe.db.commit()
name_list.append(name)
except Exception:
- frappe.log_error(frappe.get_traceback())
frappe.db.rollback()
+ frappe.log_error(frappe.get_traceback())
return name_list
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 22c9257..a11f63f 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -12,7 +12,7 @@
source_link = "https://github.com/frappe/erpnext"
develop_version = '11.x.x-develop'
-staging_version = '11.0.2'
+staging_version = '11.0.3'
error_report_email = "support@erpnext.com"
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
index c91bb8f..3f6cb44 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py
@@ -16,16 +16,23 @@
self.update_new_bom()
bom_list = self.get_parent_boms(self.new_bom)
updated_bom = []
+
for bom in bom_list:
- bom_obj = frappe.get_doc("BOM", bom)
- bom_obj.get_doc_before_save()
- updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
- bom_obj.calculate_cost()
- bom_obj.update_parent_cost()
- bom_obj.db_update()
- if (getattr(bom_obj.meta, 'track_changes', False)
- and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
- bom_obj.save_version()
+ try:
+ bom_obj = frappe.get_doc("BOM", bom)
+ bom_obj.get_doc_before_save()
+ updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
+ bom_obj.calculate_cost()
+ bom_obj.update_parent_cost()
+ bom_obj.db_update()
+ if (getattr(bom_obj.meta, 'track_changes', False)
+ and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
+ bom_obj.save_version()
+
+ frappe.db.commit()
+ except Exception:
+ frappe.db.rollback()
+ frappe.log_error(frappe.get_traceback())
def validate_bom(self):
if cstr(self.current_bom) == cstr(self.new_bom):
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index bd01ec9..d285704 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -214,8 +214,9 @@
"ifnull(to_date, '2500-01-01')": [">=", today()]})
for loyalty_program in loyalty_programs:
- customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)]
- customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)]
+ customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
+ customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
+
if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
and (not loyalty_program.customer_territory or doc.territory in customer_territories):
lp_details.append(loyalty_program.name)
diff --git a/erpnext/selling/doctype/sales_team/sales_team.json b/erpnext/selling/doctype/sales_team/sales_team.json
index c77f9f4..04027b2 100644
--- a/erpnext/selling/doctype/sales_team/sales_team.json
+++ b/erpnext/selling/doctype/sales_team/sales_team.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
@@ -9,18 +10,24 @@
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
+ "engine": "InnoDB",
"fields": [
{
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
+ "columns": 0,
"fieldname": "sales_person",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
- "in_filter": 1,
+ "in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Sales Person",
"length": 0,
"no_copy": 0,
@@ -32,24 +39,31 @@
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0,
"width": "200px"
},
{
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
+ "columns": 0,
"fieldname": "contact_no",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Contact No.",
"length": 0,
"no_copy": 0,
@@ -60,24 +74,31 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0,
"width": "100px"
},
{
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
+ "columns": 0,
"fieldname": "allocated_percentage",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Contribution (%)",
"length": 0,
"no_copy": 0,
@@ -88,24 +109,31 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0,
"width": "100px"
},
{
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
+ "columns": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Contribution to Net Total",
"length": 0,
"no_copy": 0,
@@ -117,24 +145,63 @@
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
+ "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
+ "translatable": 0,
"unique": 0,
"width": "120px"
},
{
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "commission_rate",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 1,
+ "in_standard_filter": 0,
+ "label": "Commission Rate",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
+ "columns": 0,
"fieldname": "incentives",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
+ "in_global_search": 0,
"in_list_view": 1,
+ "in_standard_filter": 0,
"label": "Incentives",
"length": 0,
"no_copy": 0,
@@ -145,24 +212,26 @@
"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,
+ "translatable": 0,
"unique": 0
}
],
+ "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
-
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2016-07-11 03:28:08.421297",
+ "modified": "2018-09-17 13:03:14.755974",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Team",
@@ -171,5 +240,8 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
- "track_seen": 0
+ "show_name_in_global_search": 0,
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/__init__.py b/erpnext/selling/report/sales_person_commission_summary/__init__.py
similarity index 100%
rename from __init__.py
rename to erpnext/selling/report/sales_person_commission_summary/__init__.py
diff --git a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js
new file mode 100644
index 0000000..ba6ee78
--- /dev/null
+++ b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js
@@ -0,0 +1,54 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Sales Person Commission Summary"] = {
+ "filters": [
+
+ {
+ fieldname: "sales_person",
+ label: __("Sales Person"),
+ fieldtype: "Link",
+ options: "Sales Person"
+ },
+ {
+ fieldname: "doc_type",
+ label: __("Document Type"),
+ fieldtype: "Select",
+ options: "Sales Order\nDelivery Note\nSales Invoice",
+ default: "Sales Order"
+ },
+ {
+ fieldname: "from_date",
+ label: __("From Date"),
+ fieldtype: "Date",
+ default: frappe.defaults.get_user_default("year_start_date"),
+ },
+ {
+ fieldname:"to_date",
+ label: __("To Date"),
+ fieldtype: "Date",
+ default: frappe.datetime.get_today()
+ },
+ {
+ fieldname:"company",
+ label: __("Company"),
+ fieldtype: "Link",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company")
+ },
+ {
+ fieldname:"customer",
+ label: __("Customer"),
+ fieldtype: "Link",
+ options: "Customer",
+ },
+ {
+ fieldname:"territory",
+ label: __("Territory"),
+ fieldtype: "Link",
+ options: "Territory",
+ },
+
+ ]
+}
diff --git a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.json b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.json
new file mode 100644
index 0000000..d5ad9f1
--- /dev/null
+++ b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 1,
+ "creation": "2018-09-11 17:49:27.256304",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2018-09-11 17:49:27.256304",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Person Commission Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Order",
+ "report_name": "Sales Person Commission Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Sales Manager"
+ },
+ {
+ "role": "Maintenance User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py
new file mode 100644
index 0000000..0c84909
--- /dev/null
+++ b/erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py
@@ -0,0 +1,142 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import msgprint, _
+from frappe.utils import flt
+
+
+def execute(filters=None):
+ if not filters: filters = {}
+
+ columns = get_columns(filters)
+ entries = get_entries(filters)
+ data = []
+
+ for d in entries:
+ data.append([
+ d.name, d.customer, d.territory, d.posting_date,
+ d.base_net_amount, d.sales_person, d.allocated_percentage, d.commission_rate, d.allocated_amount,d.incentives
+ ])
+
+ if data:
+ total_row = [""]*len(data[0])
+ data.append(total_row)
+
+ return columns, data
+
+def get_columns(filters):
+ if not filters.get("doc_type"):
+ msgprint(_("Please select the document type first"), raise_exception=1)
+
+ columns =[
+ {
+ "label": _(filters["doc_type"]),
+ "options": filters["doc_type"],
+ "fieldname": filters['doc_type'],
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Customer"),
+ "options": "Customer",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Territory"),
+ "options": "Territory",
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "width": 100
+ },
+ {
+ "label": _("Posting Date"),
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "width": 100
+ },
+ {
+ "label": _("Amount"),
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 120
+ },
+ {
+ "label": _("Sales Person"),
+ "options": "Sales Person",
+ "fieldname": "sales_person",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Contribution %"),
+ "fieldname": "contribution_percentage",
+ "fieldtype": "Data",
+ "width": 110
+ },
+ {
+ "label": _("Commission Rate %"),
+ "fieldname": "commission_rate",
+ "fieldtype": "Data",
+ "width": 100
+ },
+ {
+ "label": _("Contribution Amount"),
+ "fieldname": "contribution_amount",
+ "fieldtype": "Currency",
+ "width": 120
+ },
+ {
+ "label": _("Incentives"),
+ "fieldname": "incentives",
+ "fieldtype": "Currency",
+ "width": 120
+ }
+ ]
+
+ return columns
+
+def get_entries(filters):
+ date_field = filters["doc_type"] == "Sales Order" and "transaction_date" or "posting_date"
+
+ conditions, values = get_conditions(filters, date_field)
+ entries = frappe.db.sql("""
+ select
+ dt.name, dt.customer, dt.territory, dt.%s as posting_date,dt.base_net_total as base_net_amount,
+ st.commission_rate,st.sales_person, st.allocated_percentage, st.allocated_amount, st.incentives
+ from
+ `tab%s` dt, `tabSales Team` st
+ where
+ st.parent = dt.name and st.parenttype = %s
+ and dt.docstatus = 1 %s order by dt.name desc,st.sales_person
+ """ %(date_field, filters["doc_type"], '%s', conditions),
+ tuple([filters["doc_type"]] + values), as_dict=1)
+
+ return entries
+
+def get_conditions(filters, date_field):
+ conditions = [""]
+ values = []
+
+ for field in ["company", "customer", "territory"]:
+ if filters.get(field):
+ conditions.append("dt.{0}=%s".format(field))
+ values.append(filters[field])
+
+ if filters.get("sales_person"):
+ conditions.append("st.sales_person = '{0}'".format(filters.get("sales_person")))
+
+ if filters.get("from_date"):
+ conditions.append("dt.{0}>=%s".format(date_field))
+ values.append(filters["from_date"])
+
+ if filters.get("to_date"):
+ conditions.append("dt.{0}<=%s".format(date_field))
+ values.append(filters["to_date"])
+
+ return " and ".join(conditions), values
+
+
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 325f08a..b7b186a 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -12,6 +12,8 @@
erpnext.selling.SellingController = erpnext.TransactionController.extend({
setup: function() {
this._super();
+ this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate");
+ this.frm.add_fetch("sales_person", "commission_rate", "commission_rate");
},
onload: function() {
@@ -29,8 +31,6 @@
setup_queries: function() {
var me = this;
- this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate");
-
$.each([["customer", "customer"],
["lead", "lead"]],
function(i, opts) {
@@ -171,17 +171,26 @@
allocated_percentage: function(doc, cdt, cdn) {
var sales_person = frappe.get_doc(cdt, cdn);
-
if(sales_person.allocated_percentage) {
+
sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
precision("allocated_percentage", sales_person));
+
sales_person.allocated_amount = flt(this.frm.doc.base_net_total *
sales_person.allocated_percentage / 100.0,
precision("allocated_amount", sales_person));
+ refresh_field(["allocated_amount"], sales_person);
- refresh_field(["allocated_percentage", "allocated_amount"], sales_person.name,
+ this.calculate_incentive(sales_person);
+ refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name,
sales_person.parentfield);
- }
+ }
+ },
+
+ sales_person: function(doc, cdt, cdn) {
+ var row = frappe.get_doc(cdt, cdn);
+ this.calculate_incentive(row);
+ refresh_field("incentives",row.name,row.parentfield);
},
warehouse: function(doc, cdt, cdn) {
@@ -250,6 +259,15 @@
});
},
+ calculate_incentive: function(row) {
+ if(row.allocated_amount)
+ {
+ row.incentives = flt(
+ row.allocated_amount * row.commission_rate / 100.0,
+ precision("incentives", sales_person));
+ }
+ },
+
batch_no: function(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);
diff --git a/erpnext/setup/doctype/sales_person/sales_person.js b/erpnext/setup/doctype/sales_person/sales_person.js
index 2388739..584f879 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.js
+++ b/erpnext/setup/doctype/sales_person/sales_person.js
@@ -1,6 +1,15 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
+frappe.ui.form.on('Sales Person', {
+ refresh: function(frm) {
+ if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
+ var info = frm.doc.__onload.dashboard_info;
+ frm.dashboard.add_indicator(__('Total Contribution Amount: {0}',
+ [format_currency(info.allocated_amount, info.currency)]), 'blue');
+ }
+ }
+});
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
diff --git a/erpnext/setup/doctype/sales_person/sales_person.json b/erpnext/setup/doctype/sales_person/sales_person.json
index 715d8da..7eeb500 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.json
+++ b/erpnext/setup/doctype/sales_person/sales_person.json
@@ -1,6 +1,6 @@
{
"allow_copy": 0,
- "allow_guest_to_view": 0,
+ "allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:sales_person_name",
@@ -15,7 +15,8 @@
"engine": "InnoDB",
"fields": [
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -42,11 +43,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -74,11 +76,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "translatable": 0,
+ "unique": 1
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -108,11 +111,44 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "commission_rate",
+ "fieldtype": "Data",
+ "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": "Commission Rate",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -141,47 +177,49 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "default": "1",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "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": "Enabled",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "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,
- "translatable": 0,
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "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": "Enabled",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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,
+ "translatable": 0,
"unique": 0
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "cb0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -202,11 +240,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -233,18 +272,19 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fetch_from": "employee.department",
- "fieldname": "department",
- "fieldtype": "Link",
+ "fetch_from": "employee.department",
+ "fieldname": "department",
+ "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -252,25 +292,26 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
- "label": "Department",
+ "label": "Department",
"length": 0,
"no_copy": 0,
- "options": "Department",
+ "options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
- "read_only": 1,
+ "read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -298,11 +339,12 @@
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -330,11 +372,12 @@
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -362,11 +405,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -395,11 +439,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -428,11 +473,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
},
{
- "allow_bulk_edit": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -462,11 +508,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
- "translatable": 0,
+ "translatable": 0,
"unique": 0
}
],
- "has_web_view": 0,
+ "has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-user",
@@ -477,7 +523,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-05-17 09:44:18.203325",
+ "modified": "2018-09-12 16:41:06.378899",
"modified_by": "Administrator",
"module": "Setup",
"name": "Sales Person",
@@ -541,12 +587,13 @@
"write": 1
}
],
- "quick_entry": 0,
+ "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "parent_sales_person",
"show_name_in_global_search": 1,
"sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0
+ "track_changes": 1,
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index 816ee84..ab65f74 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -6,9 +6,10 @@
from frappe import _
from frappe.utils import flt
from frappe.utils.nestedset import NestedSet
+from erpnext import get_default_currency
class SalesPerson(NestedSet):
- nsm_parent_field = 'parent_sales_person';
+ nsm_parent_field = 'parent_sales_person'
def validate(self):
for d in self.get('targets') or []:
@@ -16,6 +17,24 @@
frappe.throw(_("Either target qty or target amount is mandatory."))
self.validate_employee_id()
+ def onload(self):
+ self.load_dashboard_info()
+
+ def load_dashboard_info(self):
+ company_default_currency = get_default_currency()
+
+ allocated_amount = frappe.db.sql("""
+ select sum(allocated_amount)
+ from `tabSales Team`
+ where sales_person = %s and docstatus=1 and parenttype = 'Sales Order'
+ """,(self.sales_person_name))
+
+ info = {}
+ info["allocated_amount"] = flt(allocated_amount[0][0]) if allocated_amount else 0
+ info["currency"] = company_default_currency
+
+ self.set_onload('dashboard_info', info)
+
def on_update(self):
super(SalesPerson, self).on_update()
self.validate_one_root()
@@ -35,4 +54,48 @@
frappe.throw(_("Another Sales Person {0} exists with the same Employee id").format(sales_person))
def on_doctype_update():
- frappe.db.add_index("Sales Person", ["lft", "rgt"])
\ No newline at end of file
+ frappe.db.add_index("Sales Person", ["lft", "rgt"])
+
+def get_timeline_data(doctype, name):
+
+ out = {}
+
+ out.update(dict(frappe.db.sql('''select
+ unix_timestamp(dt.transaction_date), count(st.parenttype)
+ from
+ `tabSales Order` dt, `tabSales Team` st
+ where
+ st.sales_person = %s and st.parent = dt.name and dt.transaction_date > date_sub(curdate(), interval 1 year)
+ group by dt.transaction_date ''', name)))
+
+ sales_invoice = dict(frappe.db.sql('''select
+ unix_timestamp(dt.posting_date), count(st.parenttype)
+ from
+ `tabSales Invoice` dt, `tabSales Team` st
+ where
+ st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
+ group by dt.posting_date ''', name))
+
+ for key in sales_invoice:
+ if out.get(key):
+ out[key] += sales_invoice[key]
+ else:
+ out[key] = sales_invoice[key]
+
+ delivery_note = dict(frappe.db.sql('''select
+ unix_timestamp(dt.posting_date), count(st.parenttype)
+ from
+ `tabDelivery Note` dt, `tabSales Team` st
+ where
+ st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
+ group by dt.posting_date ''', name))
+
+ for key in delivery_note:
+ if out.get(key):
+ out[key] += delivery_note[key]
+ else:
+ out[key] = delivery_note[key]
+
+ return out
+
+
diff --git a/erpnext/setup/doctype/sales_person/sales_person_dashboard.py b/erpnext/setup/doctype/sales_person/sales_person_dashboard.py
new file mode 100644
index 0000000..42528d8
--- /dev/null
+++ b/erpnext/setup/doctype/sales_person/sales_person_dashboard.py
@@ -0,0 +1,14 @@
+from frappe import _
+
+def get_data():
+ return {
+ 'heatmap': True,
+ 'heatmap_message': _('This is based on transactions against this Sales Person. See timeline below for details'),
+ 'fieldname': 'sales_person',
+ 'transactions': [
+ {
+ 'label': _('Sales'),
+ 'items': ['Sales Order', 'Delivery Note', 'Sales Invoice']
+ },
+ ]
+ }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 872daba..598504a 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -191,14 +191,14 @@
update_serial_nos(sle, item_det)
def validate_serial_no(sle, item_det):
+ serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
if item_det.has_serial_no==0:
- if sle.serial_no:
+ if serial_nos:
frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
SerialNoNotRequiredError)
elif sle.is_cancelled == "No":
- if sle.serial_no:
- serial_nos = get_serial_nos(sle.serial_no)
+ if serial_nos:
if cint(sle.actual_qty) != flt(sle.actual_qty):
frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
@@ -285,6 +285,12 @@
elif sle.actual_qty < 0 or not item_det.serial_no_series:
frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
SerialNoRequiredError)
+ elif serial_nos:
+ for serial_no in serial_nos:
+ sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1)
+ if sr and sle.actual_qty < 0 and sr.warehouse != sle.warehouse:
+ frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}")
+ .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse))
def validate_so_serial_no(sr, sales_order,):
if not sr.sales_order or sr.sales_order!= sales_order: