mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-29 14:48:26 +00:00
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:
@@ -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
|
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.
|
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):
|
if not source_fields or not doc or ctx.get("is_return") or not maintain_same_rate_enabled(ctx):
|
||||||
return None
|
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:
|
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"):
|
if ctx.get("is_internal_supplier"):
|
||||||
return False
|
return False
|
||||||
return bool(cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate")))
|
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):
|
def apply_price_list_on_item(ctx, doc=None):
|
||||||
item_doc = frappe.get_cached_doc("Item", ctx.item_code)
|
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(
|
ctx.conversion_factor = flt(ctx.conversion_factor) or get_conversion_factor(ctx.item_code, ctx.uom).get(
|
||||||
"conversion_factor", 1
|
"conversion_factor", 1
|
||||||
|
|||||||
@@ -196,3 +196,66 @@ class TestGetItemDetail(ERPNextTestSuite):
|
|||||||
# Control: without the setting the newer Item Price would be fetched.
|
# Control: without the setting the newer Item Price would be fetched.
|
||||||
set_maintain_same_rate(0)
|
set_maintain_same_rate(0)
|
||||||
self.assertEqual(fetch_price_list_rate(), 120)
|
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")
|
||||||
|
|||||||
Reference in New Issue
Block a user