From 1cfe2d7b457cb90aca28119324476b6878dada49 Mon Sep 17 00:00:00 2001 From: shreyas Date: Wed, 19 Oct 2016 18:36:12 +0530 Subject: [PATCH 1/4] [Minor] Validate purchase price against selling price --- erpnext/controllers/selling_controller.py | 16 +++- .../selling_settings/selling_settings.js | 8 ++ .../selling_settings/selling_settings.json | 88 ++++++++++++++++++- 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 erpnext/selling/doctype/selling_settings/selling_settings.js diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 3f4d12d20b..d1d1d9469e 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -32,6 +32,7 @@ class SellingController(StockController): def validate(self): super(SellingController, self).validate() self.validate_max_discount() + self.validate_selling_price() check_active_sales_items(self) def set_missing_values(self, for_validate=False): @@ -161,6 +162,17 @@ class SellingController(StockController): if discount and flt(d.discount_percentage) > discount: frappe.throw(_("Maxiumm discount for Item {0} is {1}%").format(d.item_code, discount)) + def validate_selling_price(self): + selling_settings = frappe.get_single("Selling Settings") + if not selling_settings.validate_selling_price_purchase_rate: + return + + for it in self.get("items"): + item = frappe.get_doc("Item", it.name) + + if flt(it.base_rate) < flt(item.last_purchase_rate): + frappe.throw(_("Selling price for item {0} is lower than its Purchase rate. Selling price should be atleast {1}").format(it.item_name, item.last_purchase_rate)) + def get_item_list(self): il = [] for d in self.get("items"): @@ -230,7 +242,7 @@ class SellingController(StockController): status = frappe.db.get_value("Sales Order", d.get(ref_fieldname), "status") if status == "Closed": frappe.throw(_("Sales Order {0} is {1}").format(d.get(ref_fieldname), status)) - + def update_reserved_qty(self): so_map = {} for d in self.get("items"): @@ -310,7 +322,7 @@ def check_active_sales_items(obj): item = frappe.db.sql("""select docstatus, income_account from tabItem where name = %s""", d.item_code, as_dict=True)[0] - + if getattr(d, "income_account", None) and not item.income_account: frappe.db.set_value("Item", d.item_code, "income_account", d.income_account) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.js b/erpnext/selling/doctype/selling_settings/selling_settings.js new file mode 100644 index 0000000000..cf6fb2806e --- /dev/null +++ b/erpnext/selling/doctype/selling_settings/selling_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Selling Settings', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index affa38b88b..6d84730ce1 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -2,29 +2,36 @@ "allow_copy": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2013-06-25 10:25:16", "custom": 0, "description": "Settings for Selling Module", "docstatus": 0, "doctype": "DocType", "document_type": "Other", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "Customer Name", "fieldname": "cust_master_name", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Customer Naming By", + "length": 0, "no_copy": 0, "options": "Customer Name\nNaming Series", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -36,17 +43,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "campaign_naming_by", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Campaign Naming By", + "length": 0, "no_copy": 0, "options": "Campaign Name\nNaming Series", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -58,18 +69,22 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "", "fieldname": "customer_group", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Default Customer Group", + "length": 0, "no_copy": 0, "options": "Customer Group", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -81,18 +96,22 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "", "fieldname": "territory", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Default Territory", + "length": 0, "no_copy": 0, "options": "Territory", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -104,17 +123,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "selling_price_list", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 1, "label": "Default Price List", + "length": 0, "no_copy": 0, "options": "Price List", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -126,15 +149,19 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break_5", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -146,17 +173,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "so_required", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Sales Order Required", + "length": 0, "no_copy": 0, "options": "No\nYes", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -168,17 +199,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "dn_required", "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Delivery Note Required", + "length": 0, "no_copy": 0, "options": "No\nYes", "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -190,16 +225,20 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "maintain_same_sales_rate", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Maintain Same Rate Throughout Sales Cycle", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -211,16 +250,20 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "editable_price_list_rate", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Allow user to edit Price List Rate in transactions", + "length": 0, "no_copy": 0, "permlevel": 0, "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -232,17 +275,21 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "allow_multiple_items", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Allow Item to be added multiple times in a transaction", + "length": 0, "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -254,17 +301,47 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "allow_against_multiple_purchase_orders", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, "label": "Allow multiple Sales Orders against a Customer's Purchase Order", + "length": 0, "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "validate_selling_price_purchase_rate", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Validate Selling Price for Item against Purchase Rate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, "report_hide": 0, "reqd": 0, @@ -277,12 +354,14 @@ "hide_toolbar": 0, "icon": "icon-cog", "idx": 1, + "image_view": 0, "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-08-27 02:42:56.512460", + "max_attachments": 0, + "modified": "2016-10-19 18:32:19.667032", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -298,6 +377,7 @@ "export": 0, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -309,6 +389,10 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 } \ No newline at end of file From edcba51c8b6715cf7a6f4e42a1f1f5adb50574f5 Mon Sep 17 00:00:00 2001 From: shreyas Date: Thu, 20 Oct 2016 11:50:00 +0530 Subject: [PATCH 2/4] [Fix] Added check to validate selling price against valuation rate --- erpnext/controllers/selling_controller.py | 7 ++++--- .../selling/doctype/selling_settings/selling_settings.json | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index d1d1d9469e..e200bf7923 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -164,14 +164,15 @@ class SellingController(StockController): def validate_selling_price(self): selling_settings = frappe.get_single("Selling Settings") - if not selling_settings.validate_selling_price_purchase_rate: + if not selling_settings.validate_selling_price: return for it in self.get("items"): item = frappe.get_doc("Item", it.name) - if flt(it.base_rate) < flt(item.last_purchase_rate): - frappe.throw(_("Selling price for item {0} is lower than its Purchase rate. Selling price should be atleast {1}").format(it.item_name, item.last_purchase_rate)) + if flt(it.base_rate) < flt(item.last_purchase_rate) or flt(it.base_rate) < flt(item.valuation_rate): + frappe.throw(_("""Selling price for item {0} is lower than its Purchase rate or Valuation rate. + Selling price should be atleast {1}""").format(it.item_name, item.last_purchase_rate)) def get_item_list(self): il = [] diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 6d84730ce1..dd9bcd70c4 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -328,14 +328,14 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "validate_selling_price_purchase_rate", + "fieldname": "validate_selling_price", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Validate Selling Price for Item against Purchase Rate", + "label": "Validate Selling Price for Item against Purchase Rate or Valuation Rate", "length": 0, "no_copy": 0, "permlevel": 0, @@ -361,7 +361,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-10-19 18:32:19.667032", + "modified": "2016-10-20 08:17:45.621151", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From e8aaa92816ef54e96af4f149f699e8a8ba7dce0b Mon Sep 17 00:00:00 2001 From: shreyas Date: Thu, 20 Oct 2016 14:03:59 +0530 Subject: [PATCH 3/4] [Minor] Some more fixes to the code --- erpnext/controllers/selling_controller.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e200bf7923..2a43b39714 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -8,6 +8,7 @@ from erpnext.setup.utils import get_company_currency from frappe import _, throw from erpnext.stock.get_item_details import get_bin_details from erpnext.stock.utils import get_incoming_rate +from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.controllers.stock_controller import StockController @@ -168,11 +169,25 @@ class SellingController(StockController): return for it in self.get("items"): - item = frappe.get_doc("Item", it.name) + last_purchase_rate = frappe.db.get_value("Item", it.name, "last_purchase_rate") - if flt(it.base_rate) < flt(item.last_purchase_rate) or flt(it.base_rate) < flt(item.valuation_rate): - frappe.throw(_("""Selling price for item {0} is lower than its Purchase rate or Valuation rate. - Selling price should be atleast {1}""").format(it.item_name, item.last_purchase_rate)) + if flt(it.base_rate) < flt(last_purchase_rate): + throw(it.name, last_purchase_rate) + + last_valuation_rate = frappe.db.sql(""" + SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s + AND warehouse = %s AND valuation_rate > 0 + ORDER BY posting_date DESC, posting_time DESC, name DESC LIMIT 1 + """, (it.item_code, it.warehouse)) + + is_stock_item = frappe.db.get_value("Item", it.name, "is_stock_item") + + if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate): + throw(it.name, last_valuation_rate) + + def throw(item_name, rate): + frappe.throw(_("""Selling price for item {0} is lower than its Purchase rate or Valuation rate. + Selling price should be atleast {1}""").format(item_name, rate)) def get_item_list(self): il = [] From 9f26bc16b79dfee5f989f1ee1e8c5a27a7d203a8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 20 Oct 2016 16:45:53 +0530 Subject: [PATCH 4/4] Update selling_controller.py --- erpnext/controllers/selling_controller.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 2a43b39714..68e91550a8 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -164,15 +164,14 @@ class SellingController(StockController): frappe.throw(_("Maxiumm discount for Item {0} is {1}%").format(d.item_code, discount)) def validate_selling_price(self): - selling_settings = frappe.get_single("Selling Settings") - if not selling_settings.validate_selling_price: + if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): return for it in self.get("items"): - last_purchase_rate = frappe.db.get_value("Item", it.name, "last_purchase_rate") + last_purchase_rate, is_stock_item = frappe.db.get_value("Item", it.name, ["last_purchase_rate", "is_stock_item"]) if flt(it.base_rate) < flt(last_purchase_rate): - throw(it.name, last_purchase_rate) + throw(it.name, last_purchase_rate, "last purchase rate") last_valuation_rate = frappe.db.sql(""" SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s @@ -180,14 +179,12 @@ class SellingController(StockController): ORDER BY posting_date DESC, posting_time DESC, name DESC LIMIT 1 """, (it.item_code, it.warehouse)) - is_stock_item = frappe.db.get_value("Item", it.name, "is_stock_item") - if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate): - throw(it.name, last_valuation_rate) + throw_message(it.name, last_valuation_rate, "valuation rate") - def throw(item_name, rate): - frappe.throw(_("""Selling price for item {0} is lower than its Purchase rate or Valuation rate. - Selling price should be atleast {1}""").format(item_name, rate)) + def throw_message(item_name, rate, ref_rate_field): + frappe.throw(_("""Selling price for item {0} is lower than its {1}. Selling price should be atleast {2}""") + .format(item_name, ref_rate_field, rate)) def get_item_list(self): il = []