Merge pull request #47417 from sagarvora/update-pl-based-on

This commit is contained in:
Sagar Vora
2025-05-06 16:45:26 +05:30
committed by GitHub
10 changed files with 224 additions and 62 deletions

View File

@@ -410,3 +410,4 @@ erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices erpnext.patches.v15_0.update_payment_schedule_fields_in_invoices
erpnext.patches.v15_0.rename_group_by_to_categorize_by erpnext.patches.v15_0.rename_group_by_to_categorize_by
execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor") execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor")
erpnext.patches.v14_0.set_update_price_list_based_on

View File

@@ -0,0 +1,14 @@
import frappe
from frappe.utils import cint
def execute():
frappe.db.set_single_value(
"Stock Settings",
"update_price_list_based_on",
(
"Price List Rate"
if cint(frappe.db.get_single_value("Selling Settings", "editable_price_list_rate"))
else "Rate"
),
)

View File

@@ -880,7 +880,13 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
def test_auto_insert_price(self): def test_auto_insert_price(self):
make_item("_Test Item for Auto Price List", {"is_stock_item": 0}) make_item("_Test Item for Auto Price List", {"is_stock_item": 0})
make_item("_Test Item for Auto Price List with Discount Percentage", {"is_stock_item": 0}) make_item("_Test Item for Auto Price List with Discount Percentage", {"is_stock_item": 0})
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) frappe.db.set_single_value(
"Stock Settings",
{
"auto_insert_price_list_rate_if_missing": 1,
"update_price_list_based_on": "Price List Rate",
},
)
item_price = frappe.db.get_value( item_price = frappe.db.get_value(
"Item Price", {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"} "Item Price", {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}
@@ -892,6 +898,7 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
item_code="_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100 item_code="_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100
) )
# ensure price gets inserted based on rate if price list rate is not defined by user
self.assertEqual( self.assertEqual(
frappe.db.get_value( frappe.db.get_value(
"Item Price", "Item Price",
@@ -901,6 +908,8 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
100, 100,
) )
# ensure price gets insterted based on user-defined *Price List Rate*
# if update_price_list_based_on is set to Price List Rate
make_sales_order( make_sales_order(
item_code="_Test Item for Auto Price List with Discount Percentage", item_code="_Test Item for Auto Price List with Discount Percentage",
selling_price_list="_Test Price List", selling_price_list="_Test Price List",
@@ -908,18 +917,43 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
discount_percentage=20, discount_percentage=20,
) )
self.assertEqual( item_price = frappe.db.get_value(
frappe.db.get_value( "Item Price",
"Item Price", {
{ "price_list": "_Test Price List",
"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List with Discount Percentage",
"item_code": "_Test Item for Auto Price List with Discount Percentage", },
}, ("name", "price_list_rate"),
"price_list_rate", as_dict=True,
),
200,
) )
self.assertEqual(item_price.price_list_rate, 200)
frappe.delete_doc("Item Price", item_price.name)
frappe.db.set_single_value("Stock Settings", "update_price_list_based_on", "Rate")
# ensure price gets insterted based on user-defined *Rate*
# if update_price_list_based_on is set to Rate
make_sales_order(
item_code="_Test Item for Auto Price List with Discount Percentage",
selling_price_list="_Test Price List",
price_list_rate=200,
discount_percentage=20,
)
item_price = frappe.db.get_value(
"Item Price",
{
"price_list": "_Test Price List",
"item_code": "_Test Item for Auto Price List with Discount Percentage",
},
("name", "price_list_rate"),
as_dict=True,
)
self.assertEqual(item_price.price_list_rate, 160)
frappe.delete_doc("Item Price", item_price.name)
# do not update price list # do not update price list
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
@@ -944,6 +978,63 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1) frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1)
def test_update_existing_item_price(self):
item_code = "_Test Item for Price List Updation"
price_list = "_Test Price List"
make_item(item_code, {"is_stock_item": 0})
frappe.db.set_single_value(
"Stock Settings",
{
"auto_insert_price_list_rate_if_missing": 1,
"update_existing_price_list_rate": 1,
"update_price_list_based_on": "Rate",
},
)
# setup: price creation
make_sales_order(item_code=item_code, selling_price_list=price_list, rate=100)
# test price updation based on Rate
make_sales_order(item_code=item_code, selling_price_list=price_list, rate=90)
self.assertEqual(
frappe.db.get_value(
"Item Price",
{"price_list": price_list, "item_code": item_code},
"price_list_rate",
),
90,
)
frappe.db.set_single_value(
"Stock Settings",
{
"update_price_list_based_on": "Price List Rate",
},
)
# test price updation based on Price List Rate
make_sales_order(
item_code=item_code,
selling_price_list=price_list,
price_list_rate=200,
discount_percentage=20,
)
self.assertEqual(
frappe.db.get_value(
"Item Price",
{"price_list": price_list, "item_code": item_code},
"price_list_rate",
),
200,
)
# reset `update_existing_price_list_rate` to 0
frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0)
def test_drop_shipping(self): def test_drop_shipping(self):
from erpnext.buying.doctype.purchase_order.purchase_order import update_status from erpnext.buying.doctype.purchase_order.purchase_order import update_status
from erpnext.selling.doctype.sales_order.sales_order import ( from erpnext.selling.doctype.sales_order.sales_order import (

View File

@@ -2,5 +2,7 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Selling Settings", { frappe.ui.form.on("Selling Settings", {
refresh: function (frm) {}, after_save(frm) {
frappe.boot.user.defaults.editable_price_list_rate = frm.doc.editable_price_list_rate;
},
}); });

View File

@@ -34,6 +34,7 @@ def set_default_settings(args):
stock_settings.stock_uom = "Nos" stock_settings.stock_uom = "Nos"
stock_settings.auto_indent = 1 stock_settings.auto_indent = 1
stock_settings.auto_insert_price_list_rate_if_missing = 1 stock_settings.auto_insert_price_list_rate_if_missing = 1
stock_settings.update_price_list_based_on = "Rate"
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1 stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
stock_settings.save() stock_settings.save()

View File

@@ -503,6 +503,7 @@ def update_stock_settings():
stock_settings.stock_uom = "Nos" stock_settings.stock_uom = "Nos"
stock_settings.auto_indent = 1 stock_settings.auto_indent = 1
stock_settings.auto_insert_price_list_rate_if_missing = 1 stock_settings.auto_insert_price_list_rate_if_missing = 1
stock_settings.update_price_list_based_on = "Rate"
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1 stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
stock_settings.save() stock_settings.save()

View File

@@ -51,4 +51,30 @@ frappe.ui.form.on("Stock Settings", {
} }
); );
}, },
auto_insert_price_list_rate_if_missing(frm) {
if (!frm.doc.auto_insert_price_list_rate_if_missing) return;
frm.set_value(
"update_price_list_based_on",
cint(frappe.defaults.get_default("editable_price_list_rate")) ? "Price List Rate" : "Rate"
);
},
update_price_list_based_on(frm) {
if (
frm.doc.update_price_list_based_on === "Price List Rate" &&
!cint(frappe.defaults.get_default("editable_price_list_rate"))
) {
const dialog = frappe.warn(
__("Incompatible Setting Detected"),
__(
"<p>Price List Rate has not been set as editable in Selling Settings. In this scenario, setting <strong>Update Price List Based On</strong> to <strong>Price List Rate</strong> will prevent auto-updation of Item Price.</p>Are you sure you want to continue?"
)
);
dialog.set_secondary_action(() => {
frm.set_value("update_price_list_based_on", "Rate");
dialog.hide();
});
return;
}
},
}); });

View File

@@ -16,6 +16,7 @@
"stock_uom", "stock_uom",
"price_list_defaults_section", "price_list_defaults_section",
"auto_insert_price_list_rate_if_missing", "auto_insert_price_list_rate_if_missing",
"update_price_list_based_on",
"column_break_12", "column_break_12",
"update_existing_price_list_rate", "update_existing_price_list_rate",
"conversion_factor_section", "conversion_factor_section",
@@ -528,6 +529,15 @@
"fieldname": "allow_to_make_quality_inspection_after_purchase_or_delivery", "fieldname": "allow_to_make_quality_inspection_after_purchase_or_delivery",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow to Make Quality Inspection after Purchase / Delivery" "label": "Allow to Make Quality Inspection after Purchase / Delivery"
},
{
"default": "Rate",
"depends_on": "eval: doc.auto_insert_price_list_rate_if_missing",
"fieldname": "update_price_list_based_on",
"fieldtype": "Select",
"label": "Update Price List Based On",
"mandatory_depends_on": "eval: doc.auto_insert_price_list_rate_if_missing",
"options": "Rate\nPrice List Rate"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -535,7 +545,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2025-04-11 18:56:35.781929", "modified": "2025-05-06 02:39:24.284587",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Settings", "name": "Stock Settings",

View File

@@ -65,6 +65,7 @@ class StockSettings(Document):
stock_frozen_upto_days: DF.Int stock_frozen_upto_days: DF.Int
stock_uom: DF.Link | None stock_uom: DF.Link | None
update_existing_price_list_rate: DF.Check update_existing_price_list_rate: DF.Check
update_price_list_based_on: DF.Literal["Rate", "Price List Rate"]
use_naming_series: DF.Check use_naming_series: DF.Check
use_serial_batch_fields: DF.Check use_serial_batch_fields: DF.Check
valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"] valuation_method: DF.Literal["FIFO", "Moving Average", "LIFO"]

View File

@@ -962,8 +962,8 @@ def get_price_list_rate(ctx: ItemDetailsCtx, item_doc, out: ItemDetails = None):
price_list_rate = get_price_list_rate_for(ctx, item_doc.variant_of) price_list_rate = get_price_list_rate_for(ctx, item_doc.variant_of)
# insert in database # insert in database
if price_list_rate is None or frappe.db.get_single_value( if price_list_rate is None or frappe.get_cached_value(
"Stock Settings", "update_existing_price_list_rate" "Stock Settings", "Stock Settings", "update_existing_price_list_rate"
): ):
insert_item_price(ctx) insert_item_price(ctx)
@@ -988,54 +988,69 @@ def insert_item_price(ctx: ItemDetailsCtx):
if not ctx.price_list or not ctx.rate or ctx.is_internal_supplier or ctx.is_internal_customer: if not ctx.price_list or not ctx.rate or ctx.is_internal_supplier or ctx.is_internal_customer:
return return
if frappe.db.get_value("Price List", ctx.price_list, "currency", cache=True) == ctx.currency and cint( stock_settings = frappe.get_cached_doc("Stock Settings")
frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
):
if frappe.has_permission("Item Price", "write"):
price_list_rate = (
(flt(ctx.rate) + flt(ctx.discount_amount)) / ctx.conversion_factor
if ctx.conversion_factor
else (flt(ctx.rate) + flt(ctx.discount_amount))
)
item_price = frappe.db.get_value( if (
"Item Price", not frappe.db.get_value("Price List", ctx.price_list, "currency", cache=True) == ctx.currency
{ or not stock_settings.auto_insert_price_list_rate_if_missing
"item_code": ctx.item_code, or not frappe.has_permission("Item Price", "write")
"price_list": ctx.price_list, ):
"currency": ctx.currency, return
"uom": ctx.stock_uom,
}, item_price = frappe.db.get_value(
["name", "price_list_rate"], "Item Price",
as_dict=1, {
) "item_code": ctx.item_code,
if item_price and item_price.name: "price_list": ctx.price_list,
if item_price.price_list_rate != price_list_rate and frappe.db.get_single_value( "currency": ctx.currency,
"Stock Settings", "update_existing_price_list_rate" "uom": ctx.stock_uom,
): },
frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate) ["name", "price_list_rate"],
frappe.msgprint( as_dict=1,
_("Item Price updated for {0} in Price List {1}").format( )
ctx.item_code, ctx.price_list
), update_based_on_price_list_rate = stock_settings.update_price_list_based_on == "Price List Rate"
alert=True,
) if item_price and item_price.name:
else: if not stock_settings.update_existing_price_list_rate:
item_price = frappe.get_doc( return
{
"doctype": "Item Price", rate_to_consider = flt(ctx.price_list_rate) if update_based_on_price_list_rate else flt(ctx.rate)
"price_list": ctx.price_list, price_list_rate = _get_stock_uom_rate(rate_to_consider, ctx)
"item_code": ctx.item_code,
"currency": ctx.currency, if not price_list_rate or item_price.price_list_rate == price_list_rate:
"price_list_rate": price_list_rate, return
"uom": ctx.stock_uom,
} frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate)
) frappe.msgprint(
item_price.insert() _("Item Price updated for {0} in Price List {1}").format(ctx.item_code, ctx.price_list),
frappe.msgprint( alert=True,
_("Item Price added for {0} in Price List {1}").format(ctx.item_code, ctx.price_list), )
alert=True, else:
) rate_to_consider = (
(flt(ctx.price_list_rate) or flt(ctx.rate)) if update_based_on_price_list_rate else flt(ctx.rate)
)
price_list_rate = _get_stock_uom_rate(rate_to_consider, ctx)
item_price = frappe.get_doc(
{
"doctype": "Item Price",
"price_list": ctx.price_list,
"item_code": ctx.item_code,
"currency": ctx.currency,
"price_list_rate": price_list_rate,
"uom": ctx.stock_uom,
}
)
item_price.insert()
frappe.msgprint(
_("Item Price added for {0} in Price List {1}").format(ctx.item_code, ctx.price_list),
alert=True,
)
def _get_stock_uom_rate(rate: float, ctx: ItemDetailsCtx):
return rate / ctx.conversion_factor if ctx.conversion_factor else rate
def get_item_price( def get_item_price(