mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 11:39:18 +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.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
|
||||||
|
|||||||
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):
|
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 (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
"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",
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user