fix: cannot merge pos invoice if validate selling price is checked (#23593)
* fix: cannot merge pos invoice if validate selling price is checked
* fix: validate selling price
* fix: test
* chore: add test
* fix: error message
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 1a5920d..e08af95 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -7,6 +7,7 @@
import unittest, copy, time
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestPOSInvoice(unittest.TestCase):
def test_timestamp_change(self):
@@ -307,8 +308,9 @@
merge_pos_invoices()
pos_inv.load_from_db()
- sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice)
- self.assertEqual(sales_invoice.grand_total, 3500)
+ rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
+ self.assertEqual(rounded_total, 3500)
+ frappe.set_user("Administrator")
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
@@ -348,8 +350,55 @@
merge_pos_invoices()
pos_inv.load_from_db()
- sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice)
- self.assertEqual(sales_invoice.rounded_total, 840)
+ rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
+ self.assertEqual(rounded_total, 840)
+ frappe.set_user("Administrator")
+
+ def test_merging_with_validate_selling_price(self):
+ from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
+ from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices
+
+ if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
+ frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
+
+ make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=1, basic_rate=300)
+ frappe.db.sql("delete from `tabPOS Invoice`")
+ test_user, pos_profile = init_user_and_profile()
+ pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+ pos_inv.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+ })
+ pos_inv.append('taxes', {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 14,
+ 'included_in_print_rate': 1
+ })
+ self.assertRaises(frappe.ValidationError, pos_inv.submit)
+
+ pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
+ pos_inv2.append('payments', {
+ 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
+ })
+ pos_inv2.append('taxes', {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Service Tax - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ "description": "Service Tax",
+ "rate": 14,
+ 'included_in_print_rate': 1
+ })
+ pos_inv2.submit()
+
+ merge_pos_invoices()
+
+ pos_inv2.load_from_db()
+ rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
+ self.assertEqual(rounded_total, 400)
+ frappe.set_user("Administrator")
+ frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
def create_pos_invoice(**args):
args = frappe._dict(args)
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 0ae1759..5886171 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cint, flt, cstr, comma_or
+from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form
from frappe import _, throw
from erpnext.stock.get_item_details import get_bin_details
from erpnext.stock.utils import get_incoming_rate
@@ -173,22 +173,26 @@
def validate_selling_price(self):
def throw_message(idx, item_name, rate, ref_rate_field):
- frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""")
- .format(idx, item_name, ref_rate_field, rate))
+ bold_net_rate = frappe.bold("net rate")
+ msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""")
+ .format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate)))
+ msg += "<br><br>"
+ msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""")
+ .format(get_link_to_form("Selling Settings", "Selling Settings")))
+ frappe.throw(msg, title=_("Invalid Selling Price"))
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
return
-
if hasattr(self, "is_return") and self.is_return:
return
for it in self.get("items"):
if not it.item_code:
continue
-
+
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
- last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
- if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):
+ last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1)
+ if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
last_valuation_rate = frappe.db.sql("""
@@ -197,8 +201,8 @@
ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1
""", (it.item_code, it.warehouse))
if last_valuation_rate:
- last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
- if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
+ last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1)
+ if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \
and not self.get('is_internal_customer'):
throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")