fix(stock): ignore disabled price lists on transactions (backport #58926) (#58931)

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
mergify[bot]
2026-09-09 10:10:27 +00:00
committed by GitHub
parent 79c8e4db7f
commit 53e53390f4
8 changed files with 126 additions and 14 deletions

View File

@@ -25,6 +25,7 @@ import erpnext
from erpnext import get_company_currency
from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
from erpnext.stock.doctype.price_list.price_list import is_price_list_enabled
from erpnext.utilities.regional import temporary_flag
try:
@@ -370,12 +371,17 @@ def set_other_values(party_details, party, party_type):
def get_default_price_list(party):
"""Return default price list for party (Document object)"""
if party.get("default_price_list"):
return party.default_price_list
"""Return the first enabled default price list for party (Document object)"""
price_list = party.get("default_price_list")
if is_price_list_enabled(price_list):
return price_list
if party.doctype == "Customer":
return frappe.get_cached_value("Customer Group", party.customer_group, "default_price_list")
if party.doctype != "Customer":
return
price_list = frappe.get_cached_value("Customer Group", party.customer_group, "default_price_list")
if is_price_list_enabled(price_list):
return price_list
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
@@ -388,7 +394,7 @@ def set_price_list(party_details, party, party_type, given_price_list, pos=None)
elif pos and party_type == "Customer":
customer_price_list = frappe.get_value("Customer", party.name, "default_price_list")
if customer_price_list:
if is_price_list_enabled(customer_price_list):
price_list = customer_price_list
else:
pos_price_list = frappe.get_value("POS Profile", pos, "selling_price_list")
@@ -396,6 +402,9 @@ def set_price_list(party_details, party, party_type, given_price_list, pos=None)
else:
price_list = get_default_price_list(party) or given_price_list
if price_list and not is_price_list_enabled(price_list):
price_list = None
if price_list:
party_details.price_list_currency = frappe.db.get_value(
"Price List", price_list, "currency", cache=True

View File

@@ -1,6 +1,6 @@
import frappe
from erpnext.accounts.party import get_default_price_list
from erpnext.accounts.party import get_default_price_list, set_price_list
from erpnext.tests.utils import ERPNextTestSuite
@@ -16,3 +16,46 @@ class PartyTestCase(ERPNextTestSuite):
customer.save()
price_list = get_default_price_list(customer)
assert price_list is None
def test_disabled_party_default_should_fall_back_to_given_price_list(self):
customer = self.create_customer(default_price_list=self.create_price_list(enabled=0))
given_price_list = self.create_price_list(enabled=1)
party_details = frappe._dict()
set_price_list(party_details, customer, "Customer", given_price_list)
self.assertEqual(party_details.selling_price_list, given_price_list)
def test_disabled_given_price_list_should_not_be_set(self):
customer = self.create_customer()
party_details = frappe._dict()
set_price_list(party_details, customer, "Customer", self.create_price_list(enabled=0))
self.assertIsNone(party_details.selling_price_list)
def create_price_list(self, enabled):
price_list = frappe.get_doc(
{
"doctype": "Price List",
"price_list_name": frappe.generate_hash(length=10),
"currency": "INR",
"selling": 1,
"enabled": enabled,
}
).insert(ignore_permissions=True)
return price_list.name
def create_customer(self, **values):
customer = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": frappe.generate_hash(length=10),
**values,
}
).insert(ignore_permissions=True, ignore_mandatory=True)
customer.customer_group = None
customer.save()
return customer

View File

@@ -69,7 +69,7 @@ erpnext.buying = {
if (this.frm.fields_dict.buying_price_list) {
this.frm.set_query("buying_price_list", function () {
return {
filters: { buying: 1 },
filters: { buying: 1, enabled: 1 },
};
});
}

View File

@@ -59,7 +59,7 @@ erpnext.sales_common = {
if (this.frm.fields_dict.selling_price_list) {
this.frm.set_query("selling_price_list", function () {
return { filters: { selling: 1 } };
return { filters: { selling: 1, enabled: 1 } };
});
}

View File

@@ -7,6 +7,7 @@ from frappe.defaults import get_user_default
from frappe.utils import cint
import erpnext.accounts.utils
from erpnext.stock.doctype.price_list.price_list import is_price_list_enabled
def boot_session(bootinfo):
@@ -28,6 +29,8 @@ def boot_session(bootinfo):
frappe.get_single_value("Accounts Settings", "disable_include_dimensions")
)
remove_disabled_price_list_defaults(bootinfo)
bootinfo.sysdefaults.quotation_valid_till = cint(
frappe.db.get_single_value("CRM Settings", "default_valid_till")
)
@@ -72,6 +75,18 @@ def boot_session(bootinfo):
bootinfo.sysdefaults.repost_allowed_doctypes = frappe.get_hooks("repost_allowed_doctypes")
def remove_disabled_price_list_defaults(bootinfo):
user_defaults = (bootinfo.user or {}).get("defaults") or {}
for key in ("selling_price_list", "buying_price_list"):
price_list = bootinfo.sysdefaults.get(key) or user_defaults.get(key)
if not isinstance(price_list, str) or is_price_list_enabled(price_list):
continue
bootinfo.sysdefaults.pop(key, None)
user_defaults.pop(key, None)
def update_page_info(bootinfo):
bootinfo.page_info.update(
{

View File

@@ -0,0 +1,30 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from erpnext.tests.utils import ERPNextTestSuite
class TestBoot(ERPNextTestSuite):
def test_boot_session_drops_disabled_price_list_default(self):
from erpnext.startup.boot import boot_session
price_list = frappe.get_doc(
{
"doctype": "Price List",
"price_list_name": frappe.generate_hash(length=10),
"currency": "INR",
"selling": 1,
"enabled": 0,
}
).insert(ignore_permissions=True)
bootinfo = frappe._dict(
sysdefaults=frappe._dict(selling_price_list=price_list.name),
page_info=frappe._dict(),
docs=[],
)
boot_session(bootinfo)
self.assertIsNone(bootinfo.sysdefaults.get("selling_price_list"))

View File

@@ -32,6 +32,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.setup.doctype.brand.brand import get_brand_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.stock.doctype.price_list.price_list import is_price_list_enabled
from erpnext.stock.get_item_details import get_default_supplier, get_price_list_rate_for
from erpnext.stock.stock_balance import get_indented_qty, update_bin_qty
from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import (
@@ -219,14 +220,20 @@ class MaterialRequest(BuyingController):
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
self.validate_pp_qty()
self.set_buying_price_list()
if self.buying_price_list and not frappe.get_value("Price List", self.buying_price_list, "buying"):
def set_buying_price_list(self):
if not is_valid_buying_price_list(self.buying_price_list):
self.buying_price_list = None
if not self.buying_price_list:
buying_price_list = frappe.defaults.get_defaults().buying_price_list
if frappe.has_permission("Price List", "read", buying_price_list):
self.buying_price_list = buying_price_list
if self.buying_price_list:
return
default_price_list = frappe.defaults.get_defaults().buying_price_list
if is_valid_buying_price_list(default_price_list) and frappe.has_permission(
"Price List", "read", default_price_list
):
self.buying_price_list = default_price_list
def on_update(self):
if not self.is_new() and self.buying_price_list and self.has_value_changed("buying_price_list"):
@@ -488,6 +495,10 @@ class MaterialRequest(BuyingController):
doc.db_set("status", doc.status)
def is_valid_buying_price_list(price_list: str | None) -> bool:
return is_price_list_enabled(price_list) and bool(frappe.get_value("Price List", price_list, "buying"))
def update_completed_and_requested_qty(stock_entry, method):
if stock_entry.doctype == "Stock Entry":
material_request_map = {}

View File

@@ -86,3 +86,7 @@ def get_price_list_details(price_list):
frappe.cache().hset("price_list_details", price_list, price_list_details)
return price_list_details or {}
def is_price_list_enabled(price_list: str | None) -> bool:
return bool(price_list) and bool(frappe.get_cached_value("Price List", price_list, "enabled"))