mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 16:45:02 +00:00
Merge pull request #47417 from sagarvora/update-pl-based-on
This commit is contained in:
@@ -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
|
||||
|
||||
14
erpnext/patches/v14_0/set_update_price_list_based_on.py
Normal file
14
erpnext/patches/v14_0/set_update_price_list_based_on.py
Normal 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"
|
||||
),
|
||||
)
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user