From 99c8fb19f793f6859b339eee173b2987c339d0f2 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 20 Sep 2018 17:43:04 +0530 Subject: [PATCH 01/12] fix(style): Put markdown for badges on separate lines --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b3be965ae..fcdef97551 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@

-[![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) [https://erpnext.com](https://erpnext.com) From 6886c472513a6533b14a9b334bcd7d28c0433a05 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 20 Sep 2018 17:46:53 +0530 Subject: [PATCH 02/12] fix(style): Center align badges as well --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fcdef97551..950f282f81 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@

ERP made simple

- [![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) @@ -12,6 +11,8 @@ [https://erpnext.com](https://erpnext.com) + + 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. From 40880da28089c9f3fdcdff55649f15cb3e3d3dd3 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Thu, 20 Sep 2018 17:48:35 +0530 Subject: [PATCH 03/12] fix(readme): Add coverage badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 950f282f81..f4a08be548 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![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) From b113349f31cfb91cdea2f3e864128052f5f69543 Mon Sep 17 00:00:00 2001 From: Zarrar Date: Mon, 24 Sep 2018 14:30:07 +0530 Subject: [PATCH 04/12] minor fix for auto-opt-in for customer (#15483) --- erpnext/selling/doctype/customer/customer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index bd01ec9a91..d285704eb4 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -214,8 +214,9 @@ def get_loyalty_programs(doc): "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) From a38b77cbfde29404e0562070eb87d04487c22712 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 25 Sep 2018 18:10:50 +0530 Subject: [PATCH 05/12] feature(chart-of-accounts): Rebuild HSM Tree after bulk insertion (#15457) * feature(chart-of-accounts): Rebuild HSM Tree after bulk insertion * Update chart_of_accounts.py --- erpnext/accounts/doctype/account/account.py | 5 +++++ .../doctype/account/chart_of_accounts/chart_of_accounts.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index eef85de0b9..635a8c2327 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -12,6 +12,11 @@ class BalanceMismatchError(frappe.ValidationError): pass 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 f4cf6fabce..9b812a8b56 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 @@ import frappe, os, json 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 @@ def create_charts(company, chart_template=None, existing_company=None): _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: From 7a8c5b0c2c019063c5c98e4c1323ec6d0a4575a3 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 25 Sep 2018 18:34:33 +0530 Subject: [PATCH 06/12] fix(setup wizard): Validate FY dates (#15473) --- erpnext/public/js/setup_wizard.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 6fa710d982..484d81decc 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -138,10 +138,15 @@ erpnext.setup.slides_settings = [ validate: function () { // validate fiscal year start and end dates - if (this.values.fy_start_date == 'Invalid date' || this.values.fy_end_date == 'Invalid date') { + const invalid = this.values.fy_start_date == 'Invalid date' || + this.values.fy_end_date == 'Invalid date'; + const start_greater_than_end = this.values.fy_start_date > this.values.fy_end_date; + + if (invalid || start_greater_than_end) { frappe.msgprint(__("Please enter valid Financial Year Start and End Dates")); return false; } + return true; }, From fe1e4a41e66436e754b301d8904b6e648e4ea688 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 25 Sep 2018 18:36:32 +0530 Subject: [PATCH 07/12] Validate negative stock serial number (#15492) --- erpnext/stock/doctype/serial_no/serial_no.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index a8cab80cbe..3d1979dd81 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -187,13 +187,14 @@ def process_serial_no(sle): 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)) @@ -239,6 +240,12 @@ def validate_serial_no(sle, item_det): 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 has_duplicate_serial_no(sn, sle): if sn.warehouse: From 865cb88bdd3c4585cce8cbb62e56f763beaa70d5 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 25 Sep 2018 18:51:17 +0530 Subject: [PATCH 08/12] fix(vscode-intellisense): Make VS Code Intellisense work with ERPNext (#15453) __init__.py in apps/erpnext directory confuses vs code. Causing all erpnext dotted paths to need an extra `erpnext.` for suggestion and completion to work properly. This commit fixes this issue. --- __init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From fafc27766600f098f72b6175272bc62b724d47c3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 25 Sep 2018 18:59:20 +0530 Subject: [PATCH 09/12] [Fix] BOM update tool, too many writes in one request. Please send smaller requests (#15432) --- erpnext/accounts/doctype/sales_invoice/pos.py | 2 +- .../bom_update_tool/bom_update_tool.py | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index a5157240ac..93cabb043c 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -506,7 +506,7 @@ def save_invoice(doc, name, name_list): 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/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index c91bb8f332..3f6cb44c49 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 @@ class BOMUpdateTool(Document): 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): From 86152eb5c8ee302f38e91c520a72cf2250401adc Mon Sep 17 00:00:00 2001 From: deepeshgarg007 <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 25 Sep 2018 19:08:16 +0530 Subject: [PATCH 10/12] Sales person Commission Report and more (#15431) * Commission For Sales Person * Changes Made * Changes made in sales person dashboard and commission report * Update sales_person_commission_summary.py --- .../doctype/sales_team/sales_team.json | 80 +++++++- .../__init__.py | 0 .../sales_person_commission_summary.js | 54 +++++ .../sales_person_commission_summary.json | 26 +++ .../sales_person_commission_summary.py | 142 ++++++++++++++ erpnext/selling/sales_common.js | 28 ++- .../doctype/sales_person/sales_person.js | 9 + .../doctype/sales_person/sales_person.json | 185 +++++++++++------- .../doctype/sales_person/sales_person.py | 67 ++++++- .../sales_person/sales_person_dashboard.py | 14 ++ 10 files changed, 525 insertions(+), 80 deletions(-) create mode 100644 erpnext/selling/report/sales_person_commission_summary/__init__.py create mode 100644 erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.js create mode 100644 erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.json create mode 100644 erpnext/selling/report/sales_person_commission_summary/sales_person_commission_summary.py create mode 100644 erpnext/setup/doctype/sales_person/sales_person_dashboard.py diff --git a/erpnext/selling/doctype/sales_team/sales_team.json b/erpnext/selling/doctype/sales_team/sales_team.json index c77f9f4b2b..04027b2396 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/erpnext/selling/report/sales_person_commission_summary/__init__.py b/erpnext/selling/report/sales_person_commission_summary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 0000000000..ba6ee784b9 --- /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 0000000000..d5ad9f13e1 --- /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 0000000000..0c84909611 --- /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 325f08af42..b7b186a8d6 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -12,6 +12,8 @@ frappe.provide("erpnext.selling"); 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 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ 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 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ 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 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }); }, + 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 2388739b7f..584f879431 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 715d8dad47..7eeb500f2a 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 816ee842e3..ab65f7455e 100644 --- a/erpnext/setup/doctype/sales_person/sales_person.py +++ b/erpnext/setup/doctype/sales_person/sales_person.py @@ -6,9 +6,10 @@ import frappe 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 @@ class SalesPerson(NestedSet): 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 @@ class SalesPerson(NestedSet): 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 0000000000..42528d8832 --- /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 From e04431ea5c7f02b38f51861ed90418375a08020a Mon Sep 17 00:00:00 2001 From: Ameya Shenoy Date: Wed, 26 Sep 2018 07:26:49 +0000 Subject: [PATCH 11/12] bumped to version 10.1.54 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a7ec84ade6..68f1fa749b 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe 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''' From b8a5fb73922adcf2b91d46e6293a8d3579210d65 Mon Sep 17 00:00:00 2001 From: Ameya Shenoy Date: Wed, 26 Sep 2018 07:32:44 +0000 Subject: [PATCH 12/12] bumped to version 11.0.3 --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 22c92577e2..a11f63fc9f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" 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"