fix: keep source rate on bulk apply_price_list when maintain same rate is on

The single-row re-fetch guard skipped the bulk apply_price_list path, so
changing the price list, party, or conversion rate on a mapped transaction
re-fetched current Item Prices and overwrote the mapped rates, breaking the
maintain-same-rate check on save.

Guard apply_price_list_on_item with the same source-row lookup, and resolve the
parent doctype via ctx.parenttype since the bulk path carries the child doctype
in ctx.doctype.
This commit is contained in:
Jatin3128
2026-07-30 12:42:00 +05:30
parent 71beb6b588
commit 7f8b77bdbb
2 changed files with 76 additions and 3 deletions

View File

@@ -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

View File

@@ -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")