From 5f60c0e85ec2dbff6c36e1bf5e2fd305bb4806c8 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 2 Feb 2026 20:44:22 +0530 Subject: [PATCH] Merge pull request #52246 from mihir-kandoi/st58765 (cherry picked from commit 135a433018457345a56b834b20279d5240a00b70) # Conflicts: # erpnext/stock/doctype/delivery_note/test_delivery_note.py --- erpnext/controllers/selling_controller.py | 58 ++++---------- .../delivery_note/test_delivery_note.py | 78 +++++++++++++++++++ 2 files changed, 91 insertions(+), 45 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e661d8afbf4..7f387eea690 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -279,7 +279,7 @@ class SellingController(StockController): _( """Row #{0}: Selling rate for item {1} is lower than its {2}. Selling {3} should be atleast {4}.

Alternatively, - you can disable selling price validation in {5} to bypass + you can disable '{5}' in {6} to bypass this validation.""" ).format( idx, @@ -287,7 +287,8 @@ class SellingController(StockController): bold(ref_rate_field), bold("net rate"), bold(rate), - get_link_to_form("Selling Settings", "Selling Settings"), + bold(frappe.get_meta("Selling Settings").get_label("validate_selling_price")), + get_link_to_form("Selling Settings"), ), title=_("Invalid Selling Price"), ) @@ -298,7 +299,6 @@ class SellingController(StockController): return is_internal_customer = self.get("is_internal_customer") - valuation_rate_map = {} for item in self.items: if not item.item_code or item.is_free_item: @@ -308,7 +308,9 @@ class SellingController(StockController): "Item", item.item_code, ("last_purchase_rate", "is_stock_item") ) - last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1) + last_purchase_rate_in_sales_uom = flt( + last_purchase_rate * (item.conversion_factor or 1), item.precision("base_net_rate") + ) if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom): throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate") @@ -316,50 +318,16 @@ class SellingController(StockController): if is_internal_customer or not is_stock_item: continue - valuation_rate_map[(item.item_code, item.warehouse)] = None - - if not valuation_rate_map: - return - - or_conditions = ( - f"""(item_code = {frappe.db.escape(valuation_rate[0])} - and warehouse = {frappe.db.escape(valuation_rate[1])})""" - for valuation_rate in valuation_rate_map - ) - - valuation_rates = frappe.db.sql( - f""" - select - item_code, warehouse, valuation_rate - from - `tabBin` - where - ({" or ".join(or_conditions)}) - and valuation_rate > 0 - """, - as_dict=True, - ) - - for rate in valuation_rates: - valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate - - for item in self.items: - if not item.item_code or item.is_free_item: - continue - - last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse)) - - if not last_valuation_rate: - continue - - last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1) - - if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom): + if item.get("incoming_rate") and item.base_net_rate < ( + valuation_rate := flt( + item.incoming_rate * (item.conversion_factor or 1), item.precision("base_net_rate") + ) + ): throw_message( item.idx, item.item_name, - last_valuation_rate_in_sales_uom, - "valuation rate (Moving Average)", + valuation_rate, + "valuation rate", ) def get_item_list(self): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 29eabe2670e..65cc37cff88 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -2807,6 +2807,84 @@ class TestDeliveryNote(FrappeTestCase): frappe.db.set_single_value("System Settings", "float_precision", original_flt_precision) +<<<<<<< HEAD +======= + def test_different_rate_for_same_serial_nos(self): + item_code = make_item( + "Test Different Rate Serial No Item", + properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DRSN-.#####"}, + ).name + + se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100) + serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle) + + dn = create_delivery_note( + item_code=item_code, + qty=1, + rate=300, + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos), + ) + + dn.reload() + + sabb = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle) + for entry in sabb.entries: + self.assertEqual(entry.incoming_rate, 100) + + make_stock_entry( + item_code=item_code, + target="_Test Warehouse - _TC", + qty=1, + basic_rate=200, + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos), + ) + dn1 = create_delivery_note( + item_code=item_code, + qty=1, + rate=300, + use_serial_batch_fields=1, + serial_no="\n".join(serial_nos), + ) + + dn1.reload() + + sabb = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle) + for entry in sabb.entries: + self.assertEqual(entry.incoming_rate, 200) + + doc = frappe.new_doc("Repost Item Valuation") + doc.voucher_type = "Stock Entry" + doc.voucher_no = se.name + doc.submit() + + sabb = frappe.get_doc("Serial and Batch Bundle", dn.items[0].serial_and_batch_bundle) + for entry in sabb.entries: + self.assertEqual(entry.incoming_rate, 100) + + sabb = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle) + for entry in sabb.entries: + self.assertEqual(entry.incoming_rate, 200) + + @IntegrationTestCase.change_settings("Selling Settings", {"validate_selling_price": 1}) + def test_validate_selling_price(self): + item_code = make_item("VSP Item", properties={"is_stock_item": 1}).name + make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=10) + make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=1) + + dn = create_delivery_note( + item_code=item_code, + qty=1, + rate=9, + do_not_save=True, + ) + self.assertRaises(frappe.ValidationError, dn.save) + dn.items[0].incoming_rate = 0 + dn.items[0].stock_qty = 2 + dn.save() + +>>>>>>> 135a433018 (Merge pull request #52246 from mihir-kandoi/st58765) def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note")