From e10ab1626c1264d9d5a25216068b5d064726d59e Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 30 Nov 2021 13:24:18 +0100 Subject: [PATCH] feat: Grant commission on certain items only (#27467) Co-authored-by: Sagar Vora --- .../doctype/pos_invoice/pos_invoice.json | 10 ++- .../pos_invoice_item/pos_invoice_item.json | 11 +++- .../doctype/sales_invoice/sales_invoice.json | 11 +++- .../sales_invoice/test_sales_invoice.py | 23 +++++++ .../sales_invoice_item.json | 11 +++- .../sales_partners_commission.json | 41 ++++++------ erpnext/controllers/accounts_controller.py | 7 ++- erpnext/controllers/selling_controller.py | 28 ++++++--- .../doctype/sales_order/sales_order.json | 10 ++- .../sales_order_item/sales_order_item.json | 11 +++- erpnext/selling/sales_common.js | 63 ++++++++++--------- .../doctype/delivery_note/delivery_note.json | 8 +++ .../delivery_note_item.json | 10 ++- erpnext/stock/doctype/item/item.json | 9 ++- erpnext/stock/get_item_details.py | 3 +- 15 files changed, 190 insertions(+), 66 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index bff8587278..0c6e7edeb0 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -171,6 +171,7 @@ "sales_team_section_break", "sales_partner", "column_break10", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break2", @@ -1561,16 +1562,23 @@ "label": "Coupon Code", "options": "Coupon Code", "print_hide": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2021-08-27 20:12:57.306772", + "modified": "2021-10-05 12:11:53.871828", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index 8b71eb02fd..3f85668ede 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -46,6 +46,7 @@ "base_amount", "pricing_rules", "is_free_item", + "grant_commission", "section_break_21", "net_rate", "net_amount", @@ -800,14 +801,22 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-01-04 17:34:49.924531", + "modified": "2021-10-05 12:23:47.506290", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 93e32f1a18..545abf77e6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -182,6 +182,7 @@ "sales_team_section_break", "sales_partner", "column_break10", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break2", @@ -2019,6 +2020,12 @@ "label": "Total Billing Hours", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -2031,7 +2038,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-10-11 20:19:38.667508", + "modified": "2021-10-21 20:19:38.667508", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", @@ -2086,4 +2093,4 @@ "title_field": "title", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 37bea70f16..6a488ea96e 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2385,6 +2385,29 @@ class TestSalesInvoice(unittest.TestCase): si.reload() self.assertEqual(si.status, "Paid") + def test_sales_commission(self): + si = frappe.copy_doc(test_records[0]) + item = copy.deepcopy(si.get('items')[0]) + item.update({ + "qty": 1, + "rate": 500, + "grant_commission": 1 + }) + si.append("items", item) + + # Test valid values + for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)): + si.commission_rate = commission_rate + si.save() + self.assertEqual(si.amount_eligible_for_commission, 500) + self.assertEqual(si.total_commission, total_commission) + + # Test invalid values + for commission_rate in (101, -1): + si.reload() + si.commission_rate = commission_rate + self.assertRaises(frappe.ValidationError, si.save) + def test_sales_invoice_submission_post_account_freezing_date(self): frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1)) si = create_sales_invoice(do_not_save=True) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index b90f3f0904..ae9ac35729 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -47,6 +47,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_21", "net_rate", "net_amount", @@ -828,15 +829,23 @@ "fieldtype": "Link", "label": "Discount Account", "options": "Account" + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-08-19 13:41:53.435827", + "modified": "2021-10-05 12:24:54.968907", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json index a740de3572..9dd4e437f7 100644 --- a/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json +++ b/erpnext/accounts/report/sales_partners_commission/sales_partners_commission.json @@ -1,27 +1,30 @@ { - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2013-05-06 12:28:23", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 3, - "is_standard": "Yes", - "modified": "2017-03-06 05:52:57.645281", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Partners Commission", - "owner": "Administrator", - "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:150\",\n\tsum(base_net_total) as \"Invoiced Amount (Exclusive Tax):Currency:210\",\n\tsum(total_commission) as \"Total Commission:Currency:150\",\n\tsum(total_commission)*100/sum(base_net_total) as \"Average Commission Rate:Currency:170\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"", - "ref_doctype": "Sales Invoice", - "report_name": "Sales Partners Commission", - "report_type": "Query Report", + "add_total_row": 0, + "columns": [], + "creation": "2013-05-06 12:28:23", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 3, + "is_standard": "Yes", + "modified": "2021-10-06 06:26:07.881340", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Partners Commission", + "owner": "Administrator", + "prepared_report": 0, + "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"", + "ref_doctype": "Sales Invoice", + "report_name": "Sales Partners Commission", + "report_type": "Query Report", "roles": [ { "role": "Accounts Manager" - }, + }, { "role": "Accounts User" } ] -} +} \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 0ee884e30c..2c92820a74 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -250,7 +250,12 @@ class AccountsController(TransactionBase): from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals calculate_taxes_and_totals(self) - if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: + if self.doctype in ( + 'Sales Order', + 'Delivery Note', + 'Sales Invoice', + 'POS Invoice', + ): self.calculate_commission() self.calculate_contribution() diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index dad3ed7093..cc773b7596 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -120,13 +120,27 @@ class SellingController(StockController): self.in_words = money_in_words(amount, self.currency) def calculate_commission(self): - if self.meta.get_field("commission_rate"): - self.round_floats_in(self, ["base_net_total", "commission_rate"]) - if self.commission_rate > 100.0: - throw(_("Commission rate cannot be greater than 100")) + if not self.meta.get_field("commission_rate"): + return - self.total_commission = flt(self.base_net_total * self.commission_rate / 100.0, - self.precision("total_commission")) + self.round_floats_in( + self, ("amount_eligible_for_commission", "commission_rate") + ) + + if not (0 <= self.commission_rate <= 100.0): + throw("{} {}".format( + _(self.meta.get_label("commission_rate")), + _("must be between 0 and 100"), + )) + + self.amount_eligible_for_commission = sum( + item.base_net_amount for item in self.items if item.grant_commission + ) + + self.total_commission = flt( + self.amount_eligible_for_commission * self.commission_rate / 100.0, + self.precision("total_commission") + ) def calculate_contribution(self): if not self.meta.get_field("sales_team"): @@ -138,7 +152,7 @@ class SellingController(StockController): self.round_floats_in(sales_person) sales_person.allocated_amount = flt( - self.base_net_total * sales_person.allocated_percentage / 100.0, + self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0, self.precision("allocated_amount", sales_person)) if sales_person.commission_rate: diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 7c7ed9a960..7e99a06243 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -134,6 +134,7 @@ "sales_team_section_break", "sales_partner", "column_break7", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break1", @@ -1507,16 +1508,23 @@ "fieldtype": "Small Text", "label": "Dispatch Address", "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-09-28 13:09:51.515542", + "modified": "2021-10-05 12:16:40.775704", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 1e5590e748..95f6c4e96d 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -48,6 +48,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_24", "net_rate", "net_amount", @@ -789,15 +790,23 @@ "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-23 01:15:05.803091", + "modified": "2021-10-05 12:27:25.014789", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 20504789aa..e2e0db4044 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -157,25 +157,19 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran commission_rate() { this.calculate_commission(); - refresh_field("total_commission"); } total_commission() { - if(this.frm.doc.base_net_total) { - frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]); + frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]); - if(this.frm.doc.base_net_total < this.frm.doc.total_commission) { - var msg = (__("[Error]") + " " + - __(frappe.meta.get_label(this.frm.doc.doctype, "total_commission", - this.frm.doc.name)) + " > " + - __(frappe.meta.get_label(this.frm.doc.doctype, "base_net_total", this.frm.doc.name))); - frappe.msgprint(msg); - throw msg; - } + const { amount_eligible_for_commission } = this.frm.doc; + if(!amount_eligible_for_commission) return; - this.frm.set_value("commission_rate", - flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total)); - } + this.frm.set_value( + "commission_rate", flt( + this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission + ) + ); } allocated_percentage(doc, cdt, cdn) { @@ -185,7 +179,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran 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_amount = flt(this.frm.doc.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0, precision("allocated_amount", sales_person)); refresh_field(["allocated_amount"], sales_person); @@ -259,28 +253,39 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran } calculate_commission() { - if(this.frm.fields_dict.commission_rate) { - if(this.frm.doc.commission_rate > 100) { - var msg = __(frappe.meta.get_label(this.frm.doc.doctype, "commission_rate", this.frm.doc.name)) + - " " + __("cannot be greater than 100"); - frappe.msgprint(msg); - throw msg; - } + if(!this.frm.fields_dict.commission_rate) return; - this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0, - precision("total_commission")); + if(this.frm.doc.commission_rate > 100) { + this.frm.set_value("commission_rate", 100); + frappe.throw(`${__(frappe.meta.get_label( + this.frm.doc.doctype, "commission_rate", this.frm.doc.name + ))} ${__("cannot be greater than 100")}`); } + + this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce( + (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0 + ) + + this.frm.doc.total_commission = flt( + this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0, + precision("total_commission") + ); + + refresh_field(["amount_eligible_for_commission", "total_commission"]); } calculate_contribution() { var me = this; $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) { frappe.model.round_floats_in(sales_person); - if(sales_person.allocated_percentage) { - sales_person.allocated_amount = flt( - me.frm.doc.base_net_total * sales_person.allocated_percentage / 100.0, - precision("allocated_amount", sales_person)); - } + if (!sales_person.allocated_percentage) return; + + sales_person.allocated_amount = flt( + me.frm.doc.amount_eligible_for_commission + * sales_person.allocated_percentage + / 100.0, + precision("allocated_amount", sales_person) + ); }); } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index ad1b3b43ae..e78d5010eb 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -145,6 +145,7 @@ "sales_team_section_break", "sales_partner", "column_break7", + "amount_eligible_for_commission", "commission_rate", "total_commission", "section_break1", @@ -1302,6 +1303,12 @@ "label": "Dispatch Address", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "amount_eligible_for_commission", + "fieldtype": "Currency", + "label": "Amount Eligible for Commission", + "read_only": 1 } ], "icon": "fa fa-truck", @@ -1312,6 +1319,7 @@ "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index a96c29925e..51c88bed61 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -49,6 +49,7 @@ "pricing_rules", "stock_uom_rate", "is_free_item", + "grant_commission", "section_break_25", "net_rate", "net_amount", @@ -753,13 +754,20 @@ "no_copy": 1, "options": "currency", "read_only": 1 + }, + { + "default": "0", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-10-05 12:12:44.018872", + "modified": "2021-10-06 12:12:44.018872", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index cd97ec31a4..5469a9fc25 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -88,6 +88,7 @@ "sales_details", "sales_uom", "is_sales_item", + "grant_commission", "column_break3", "max_discount", "deferred_revenue", @@ -1020,6 +1021,12 @@ "fieldname": "website_image_alt", "fieldtype": "Data", "label": "Image Description" + }, + { + "default": "1", + "fieldname": "grant_commission", + "fieldtype": "Check", + "label": "Grant Commission" } ], "has_web_view": 1, @@ -1028,7 +1035,7 @@ "image_field": "image", "index_web_pages_for_search": 1, "links": [], - "modified": "2021-11-30 01:33:06.572442", + "modified": "2021-11-30 02:33:06.572442", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cd180a42ca..9889a22b35 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -327,7 +327,8 @@ def get_basic_details(args, item, overwrite_warehouse=True): "against_blanket_order": args.get("against_blanket_order"), "bom_no": item.get("default_bom"), "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), - "weight_uom": args.get("weight_uom") or item.get("weight_uom") + "weight_uom": args.get("weight_uom") or item.get("weight_uom"), + "grant_commission": item.get("grant_commission") }) if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):