From 526ffc11761473f4eeb49e20247314ae3d7ded3a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:51:13 +0000 Subject: [PATCH] fix: Creating new item price incase of changes in expired item price (backport #53534) (#53544) Co-authored-by: Nishka Gosalia Co-authored-by: Nishka Gosalia <58264710+nishkagosalia@users.noreply.github.com> fix: Creating new item price incase of changes in expired item price (#53534) --- .../doctype/sales_order/test_sales_order.py | 39 +++++++++++++ erpnext/stock/get_item_details.py | 58 +++++++++++++++---- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 4fffd1f801e..d7a2f9d5e26 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -57,6 +57,45 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase): def tearDown(self): frappe.set_user("Administrator") + @change_settings( + "Stock Settings", + { + "auto_insert_price_list_rate_if_missing": 1, + "update_existing_price_list_rate": 1, + "update_price_list_based_on": "Rate", + }, + ) + def test_sales_order_expired_item_price(self): + price_list = "_Test Price List" + + item_1 = make_item("_Test Expired Item 1", {"is_stock_item": 1}) + + frappe.db.delete("Item Price", {"item_code": item_1.item_code}) + + item_price = frappe.new_doc("Item Price") + item_price.item_code = item_1.item_code + item_price.price_list = price_list + item_price.price_list_rate = 100 + item_price.valid_from = add_days(today(), -10) + item_price.valid_upto = add_days(today(), -5) + item_price.save() + + so = make_sales_order( + item_code=item_1.item_code, qty=1, rate=1000, selling_price_list=price_list, do_not_save=True + ) + so.save() + so.reload() + + self.assertEqual(frappe.db.get_value("Item Price", item_price.name, "price_list_rate"), 100) + self.assertEqual( + frappe.db.count("Item Price", {"item_code": item_1.item_code, "price_list": price_list}), + 2, + ) + all_item_prices = frappe.get_all( + "Item Price", filters={"item_code": item_1.item_code}, order_by="valid_from desc" + ) + self.assertEqual(frappe.db.get_value("Item Price", all_item_prices[0].name, "price_list_rate"), 1000) + def test_sales_order_skip_delivery_note(self): so = make_sales_order(do_not_submit=True) so.order_type = "Maintenance" diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 312b8e129f8..65d04cfd80c 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -10,7 +10,7 @@ from frappe.model import child_table_fields, default_fields from frappe.model.meta import get_field_precision from frappe.model.utils import get_fetch_values from frappe.query_builder.functions import IfNull, Sum -from frappe.utils import add_days, add_months, cint, cstr, flt, getdate, parse_json +from frappe.utils import add_days, add_months, cint, cstr, flt, get_link_to_form, getdate, parse_json from erpnext import get_company_currency from erpnext.accounts.doctype.pricing_rule.pricing_rule import ( @@ -972,16 +972,30 @@ def insert_item_price(args): ): return - item_price = frappe.db.get_value( + transaction_date = ( + getdate(args.get("posting_date") or args.get("transaction_date") or args.get("posting_datetime")) + or getdate() + ) + + item_prices = frappe.get_all( "Item Price", - { + filters={ "item_code": args.item_code, "price_list": args.price_list, "currency": args.currency, "uom": args.stock_uom, }, - ["name", "price_list_rate"], - as_dict=1, + fields=["name", "price_list_rate", "valid_from", "valid_upto"], + order_by="valid_from desc, creation desc", + ) + item_price = next( + ( + row + for row in item_prices + if (not row.valid_from or getdate(row.valid_from) <= transaction_date) + and (not row.valid_upto or getdate(row.valid_upto) >= transaction_date) + ), + item_prices[0] if item_prices else None, ) update_based_on_price_list_rate = stock_settings.update_price_list_based_on == "Price List Rate" @@ -996,11 +1010,35 @@ def insert_item_price(args): if not price_list_rate or item_price.price_list_rate == price_list_rate: return - frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate) - frappe.msgprint( - _("Item Price updated for {0} in Price List {1}").format(args.item_code, args.price_list), - alert=True, - ) + is_price_valid_for_transaction = ( + not item_price.valid_from or getdate(item_price.valid_from) <= transaction_date + ) and (not item_price.valid_upto or getdate(item_price.valid_upto) >= transaction_date) + if is_price_valid_for_transaction: + frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate) + frappe.msgprint( + _("Item Price updated for {0} in Price List {1}").format( + get_link_to_form("Item", args.item_code), args.price_list + ), + alert=True, + ) + else: + # if price is not valid for the transaction date, insert a new price list rate with updated price and future validity + + item_price = frappe.new_doc( + "Item Price", + item_code=args.item_code, + price_list_rate=price_list_rate, + currency=args.currency, + uom=args.stock_uom, + price_list=args.price_list, + ) + item_price.insert() + frappe.msgprint( + _("Item Price Added for {0} in Price List {1}").format( + get_link_to_form("Item", args.item_code), args.price_list + ), + alert=True, + ) else: rate_to_consider = ( (flt(args.price_list_rate) or flt(args.rate))