From 3daad224adb02526d0276d7964054edd655aa0f5 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Oct 2020 18:31:23 +0530 Subject: [PATCH] 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 --- .../doctype/pos_invoice/test_pos_invoice.py | 57 +++++++++++++++++-- erpnext/controllers/selling_controller.py | 22 ++++--- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 1a5920d8ab..e08af952dc 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 frappe 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 @@ class TestPOSInvoice(unittest.TestCase): 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 @@ class TestPOSInvoice(unittest.TestCase): 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 0ae175989e..58861715c2 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 @@ class SellingController(StockController): 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 += "

" + 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 @@ class SellingController(StockController): 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")