Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 13 additions & 45 deletions erpnext/controllers/selling_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,16 @@ def throw_message(idx, item_name, rate, ref_rate_field):
_(
"""Row #{0}: Selling rate for item {1} is lower than its {2}.
Selling {3} should be atleast {4}.<br><br>Alternatively,
you can disable selling price validation in {5} to bypass
you can disable '{5}' in {6} to bypass
this validation."""
).format(
idx,
bold(item_name),
bold(ref_rate_field),
bold("net rate"),
bold(rate),
get_link_to_form("Selling Settings", "Selling Settings"),
bold(frappe.get_meta("Selling Settings").get_label("validate_selling_price")),
get_link_to_form("Selling Settings"),
),
title=_("Invalid Selling Price"),
)
Expand All @@ -313,7 +314,6 @@ def throw_message(idx, item_name, rate, ref_rate_field):
return

is_internal_customer = self.get("is_internal_customer")
valuation_rate_map = {}

for item in self.items:
if not item.item_code or item.is_free_item:
Expand All @@ -323,58 +323,26 @@ def throw_message(idx, item_name, rate, ref_rate_field):
"Item", item.item_code, ("last_purchase_rate", "is_stock_item")
)

last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1)
last_purchase_rate_in_sales_uom = flt(
last_purchase_rate * (item.conversion_factor or 1), item.precision("base_net_rate")
)

if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")

if is_internal_customer or not is_stock_item:
continue

valuation_rate_map[(item.item_code, item.warehouse)] = None

if not valuation_rate_map:
return

or_conditions = (
f"""(item_code = {frappe.db.escape(valuation_rate[0])}
and warehouse = {frappe.db.escape(valuation_rate[1])})"""
for valuation_rate in valuation_rate_map
)

valuation_rates = frappe.db.sql(
f"""
select
item_code, warehouse, valuation_rate
from
`tabBin`
where
({" or ".join(or_conditions)})
and valuation_rate > 0
""",
as_dict=True,
)

for rate in valuation_rates:
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate

for item in self.items:
if not item.item_code or item.is_free_item:
continue

last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse))

if not last_valuation_rate:
continue

last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)

if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
if item.get("incoming_rate") and item.base_net_rate < (
valuation_rate := flt(
item.incoming_rate * (item.conversion_factor or 1), item.precision("base_net_rate")
)
):
throw_message(
item.idx,
item.item_name,
last_valuation_rate_in_sales_uom,
"valuation rate (Moving Average)",
valuation_rate,
"valuation rate",
)

def get_item_list(self):
Expand Down
17 changes: 17 additions & 0 deletions erpnext/stock/doctype/delivery_note/test_delivery_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,23 @@ def test_different_rate_for_same_serial_nos(self):
for entry in sabb.entries:
self.assertEqual(entry.incoming_rate, 200)

@IntegrationTestCase.change_settings("Selling Settings", {"validate_selling_price": 1})
def test_validate_selling_price(self):
item_code = make_item("VSP Item", properties={"is_stock_item": 1}).name
make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=10)
make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=1)

dn = create_delivery_note(
item_code=item_code,
qty=1,
rate=9,
do_not_save=True,
)
self.assertRaises(frappe.ValidationError, dn.save)
dn.items[0].incoming_rate = 0
dn.items[0].stock_qty = 2
dn.save()


def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
Expand Down