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.rename_group_by_to_categorize_by
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):
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})
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", {"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
)
# ensure price gets inserted based on rate if price list rate is not defined by user
self.assertEqual(
frappe.db.get_value(
"Item Price",
@@ -901,6 +908,8 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
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(
item_code="_Test Item for Auto Price List with Discount Percentage",
selling_price_list="_Test Price List",
@@ -908,18 +917,43 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase):
discount_percentage=20,
)
self.assertEqual(
frappe.db.get_value(
"Item Price",
{
"price_list": "_Test Price List",
"item_code": "_Test Item for Auto Price List with Discount Percentage",
},
"price_list_rate",
),
200,
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, 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
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)
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):
from erpnext.buying.doctype.purchase_order.purchase_order import update_status
from erpnext.selling.doctype.sales_order.sales_order import (

View File

@@ -2,5 +2,7 @@
// For license information, please see license.txt
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.auto_indent = 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.save()

View File

@@ -503,6 +503,7 @@ def update_stock_settings():
stock_settings.stock_uom = "Nos"
stock_settings.auto_indent = 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.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",
"price_list_defaults_section",
"auto_insert_price_list_rate_if_missing",
"update_price_list_based_on",
"column_break_12",
"update_existing_price_list_rate",
"conversion_factor_section",
@@ -528,6 +529,15 @@
"fieldname": "allow_to_make_quality_inspection_after_purchase_or_delivery",
"fieldtype": "Check",
"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",
@@ -535,7 +545,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-04-11 18:56:35.781929",
"modified": "2025-05-06 02:39:24.284587",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",

View File

@@ -65,6 +65,7 @@ class StockSettings(Document):
stock_frozen_upto_days: DF.Int
stock_uom: DF.Link | None
update_existing_price_list_rate: DF.Check
update_price_list_based_on: DF.Literal["Rate", "Price List Rate"]
use_naming_series: DF.Check
use_serial_batch_fields: DF.Check
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)
# insert in database
if price_list_rate is None or frappe.db.get_single_value(
"Stock Settings", "update_existing_price_list_rate"
if price_list_rate is None or frappe.get_cached_value(
"Stock Settings", "Stock Settings", "update_existing_price_list_rate"
):
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:
return
if frappe.db.get_value("Price List", ctx.price_list, "currency", cache=True) == ctx.currency and cint(
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))
)
stock_settings = frappe.get_cached_doc("Stock Settings")
item_price = frappe.db.get_value(
"Item Price",
{
"item_code": ctx.item_code,
"price_list": ctx.price_list,
"currency": ctx.currency,
"uom": ctx.stock_uom,
},
["name", "price_list_rate"],
as_dict=1,
)
if item_price and item_price.name:
if item_price.price_list_rate != price_list_rate and frappe.db.get_single_value(
"Stock Settings", "update_existing_price_list_rate"
):
frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate)
frappe.msgprint(
_("Item Price updated for {0} in Price List {1}").format(
ctx.item_code, ctx.price_list
),
alert=True,
)
else:
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,
)
if (
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
or not frappe.has_permission("Item Price", "write")
):
return
item_price = frappe.db.get_value(
"Item Price",
{
"item_code": ctx.item_code,
"price_list": ctx.price_list,
"currency": ctx.currency,
"uom": ctx.stock_uom,
},
["name", "price_list_rate"],
as_dict=1,
)
update_based_on_price_list_rate = stock_settings.update_price_list_based_on == "Price List Rate"
if item_price and item_price.name:
if not stock_settings.update_existing_price_list_rate:
return
rate_to_consider = flt(ctx.price_list_rate) if update_based_on_price_list_rate else flt(ctx.rate)
price_list_rate = _get_stock_uom_rate(rate_to_consider, ctx)
if not price_list_rate or item_price.price_list_rate == price_list_rate:
return
frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate)
frappe.msgprint(
_("Item Price updated 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(