diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index 9ee05a3f6c7..ab6d1287444 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -5,7 +5,7 @@ import frappe from frappe import _, throw from frappe.model.document import Document -from frappe.utils import cint +from frappe.utils import cint, now class PriceList(Document): @@ -47,11 +47,15 @@ class PriceList(Document): frappe.set_value("Buying Settings", "Buying Settings", "buying_price_list", self.name) def update_item_price(self): - frappe.db.sql( - """update `tabItem Price` set currency=%s, - buying=%s, selling=%s, modified=NOW() where price_list=%s""", - (self.currency, cint(self.buying), cint(self.selling), self.name), - ) + item_price = frappe.qb.DocType("Item Price") + ( + frappe.qb.update(item_price) + .set(item_price.currency, self.currency) + .set(item_price.buying, cint(self.buying)) + .set(item_price.selling, cint(self.selling)) + .set(item_price.modified, now()) + .where(item_price.price_list == self.name) + ).run() def on_trash(self): self.delete_price_list_details_key() diff --git a/erpnext/stock/doctype/price_list/test_price_list.py b/erpnext/stock/doctype/price_list/test_price_list.py index 4c6b653d003..bbe56f2533f 100644 --- a/erpnext/stock/doctype/price_list/test_price_list.py +++ b/erpnext/stock/doctype/price_list/test_price_list.py @@ -1,4 +1,83 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +import frappe +from frappe.utils import random_string + +from erpnext.tests.utils import ERPNextTestSuite + + +class TestPriceList(ERPNextTestSuite): + def make_price_list(self, currency="INR", buying=1, selling=1): + price_list = frappe.get_doc( + { + "doctype": "Price List", + "price_list_name": "_Test PL " + random_string(10), + "enabled": 1, + "currency": currency, + "buying": buying, + "selling": selling, + } + ).insert() + return price_list + + def make_item_price(self, price_list, item_code="_Test Item", rate=100): + return frappe.get_doc( + { + "doctype": "Item Price", + "item_code": item_code, + "price_list": price_list, + "price_list_rate": rate, + } + ).insert() + + def test_update_item_price_propagates_currency_and_flags(self): + # Price List starts in INR, applicable for both buying and selling. + price_list = self.make_price_list(currency="INR", buying=1, selling=1) + + ip1 = self.make_item_price(price_list.name, item_code="_Test Item", rate=100) + ip2 = self.make_item_price(price_list.name, item_code="_Test Item 2", rate=250) + + # Sanity: Item Price rows inherited the Price List's initial state. + for ip in (ip1, ip2): + row = frappe.db.get_value("Item Price", ip.name, ["currency", "buying", "selling"], as_dict=True) + self.assertEqual(row.currency, "INR") + self.assertEqual(row.buying, 1) + self.assertEqual(row.selling, 1) + + # Change the Price List's currency and flip the buying flag off. + # on_update -> update_item_price() should bulk-UPDATE every Item Price + # linked to this Price List. + price_list.currency = "USD" + price_list.buying = 0 + price_list.selling = 1 + price_list.save() + + for ip in (ip1, ip2): + row = frappe.db.get_value("Item Price", ip.name, ["currency", "buying", "selling"], as_dict=True) + self.assertEqual(row.currency, "USD") + self.assertEqual(row.buying, 0) + self.assertEqual(row.selling, 1) + + def test_update_item_price_scoped_to_own_price_list(self): + # Two independent Price Lists; updating one must not touch the other's + # Item Price rows (the WHERE price_list == self.name clause). + pl_a = self.make_price_list(currency="INR", buying=1, selling=1) + pl_b = self.make_price_list(currency="INR", buying=1, selling=1) + + ip_a = self.make_item_price(pl_a.name, item_code="_Test Item", rate=100) + ip_b = self.make_item_price(pl_b.name, item_code="_Test Item", rate=100) + + pl_a.currency = "USD" + pl_a.buying = 0 + pl_a.save() + + row_a = frappe.db.get_value("Item Price", ip_a.name, ["currency", "buying"], as_dict=True) + self.assertEqual(row_a.currency, "USD") + self.assertEqual(row_a.buying, 0) + + # pl_b was untouched, so its Item Price must keep the original values. + row_b = frappe.db.get_value("Item Price", ip_b.name, ["currency", "buying"], as_dict=True) + self.assertEqual(row_b.currency, "INR") + self.assertEqual(row_b.buying, 1)