diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 8cf2fae0d83..cc8ed891770 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -210,7 +210,10 @@ def get_rate_locked_source_row(ctx: ItemDetailsCtx, doc) -> frappe._dict | None: Order) can only be saved at the source rate. Fetching a newer Item Price here would set a rate the document can never be saved with, so keep the source rate. """ - source_fields = maintain_same_rate_source_fields.get(ctx.doctype) + if isinstance(doc, str): + doc = json.loads(doc) + + source_fields = maintain_same_rate_source_fields.get(ctx.parenttype or ctx.doctype) if not source_fields or not doc or ctx.get("is_return") or not maintain_same_rate_enabled(ctx): return None @@ -221,7 +224,7 @@ def get_rate_locked_source_row(ctx: ItemDetailsCtx, doc) -> frappe._dict | None: def maintain_same_rate_enabled(ctx: ItemDetailsCtx) -> bool: - if ctx.doctype in purchase_doctypes: + if (ctx.parenttype or ctx.doctype) in purchase_doctypes: if ctx.get("is_internal_supplier"): return False return bool(cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate"))) @@ -1694,7 +1697,14 @@ def apply_price_list(ctx: ItemDetailsCtx, as_doc: bool = False, doc: Document | def apply_price_list_on_item(ctx, doc=None): item_doc = frappe.get_cached_doc("Item", ctx.item_code) - item_details = get_price_list_rate(ctx, item_doc) + + source_row = get_rate_locked_source_row(ctx, doc) + if source_row: + item_details = frappe._dict( + price_list_rate=flt(source_row.get("price_list_rate")) or flt(source_row.get("rate")) + ) + else: + item_details = get_price_list_rate(ctx, item_doc) ctx.conversion_factor = flt(ctx.conversion_factor) or get_conversion_factor(ctx.item_code, ctx.uom).get( "conversion_factor", 1 diff --git a/erpnext/stock/tests/test_get_item_details.py b/erpnext/stock/tests/test_get_item_details.py index 7f92aa0f3fa..e251323a54b 100644 --- a/erpnext/stock/tests/test_get_item_details.py +++ b/erpnext/stock/tests/test_get_item_details.py @@ -196,3 +196,66 @@ class TestGetItemDetail(ERPNextTestSuite): # Control: without the setting the newer Item Price would be fetched. set_maintain_same_rate(0) self.assertEqual(fetch_price_list_rate(), 120) + + def test_apply_price_list_keeps_source_rate_when_maintain_same_rate(self): + """#57436: the bulk apply_price_list path (price list / party / conversion rate + change) must also keep the source rate on mapped rows, not just re-fetch of a + single row. Here a PR row carries its PO rate (175) while the current price list + rate is 100; the bulk apply must keep 175. + """ + from frappe.utils import flt, nowdate + + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + from erpnext.stock.get_item_details import apply_price_list + + item_code = "_Test Item" + price_list = "_Test Buying Price List" + + original = frappe.db.get_single_value("Buying Settings", "maintain_same_rate") + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) + frappe.clear_cache(doctype="Buying Settings") + + try: + po = create_purchase_order(item_code=item_code, rate=175, qty=1) + + row_name = "pr-row-1" + pr_doc = { + "doctype": "Purchase Receipt", + "items": [ + { + "name": row_name, + "item_code": item_code, + "purchase_order_item": po.items[0].name, + "price_list_rate": 175, + "rate": 175, + } + ], + } + ctx = frappe._dict( + doctype="Purchase Receipt", + supplier=po.supplier, + company=po.company, + currency=po.currency, + conversion_rate=1.0, + price_list=price_list, + plc_conversion_rate=1.0, + transaction_date=nowdate(), + items=[ + frappe._dict( + doctype="Purchase Receipt Item", + parenttype="Purchase Receipt", + item_code=item_code, + child_docname=row_name, + qty=1, + uom=po.items[0].uom, + stock_uom=po.items[0].stock_uom, + conversion_factor=1.0, + ) + ], + ) + + result = apply_price_list(ctx, doc=pr_doc) + self.assertEqual(flt(result["children"][0].get("price_list_rate")), 175) + finally: + frappe.db.set_single_value("Buying Settings", "maintain_same_rate", original) + frappe.clear_cache(doctype="Buying Settings")