Skip to content

Commit

Permalink
fix: pricing rule item code UOM apply & conversions (backport #32566) (
Browse files Browse the repository at this point in the history
…#32637)

fix: pricing rule for non stock UOM and conversions

* fix: pricing rule for non stock UOM and conversions

(cherry picked from commit 96b4211)

Co-authored-by: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com>
  • Loading branch information
mergify[bot] and maharshivpatel committed Oct 18, 2022
1 parent 8c65863 commit ffd82f3
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 2 deletions.
18 changes: 17 additions & 1 deletion erpnext/accounts/doctype/pricing_rule/pricing_rule.py
Expand Up @@ -268,6 +268,18 @@ def get_serial_no_for_item(args):
return item_details


def update_pricing_rule_uom(pricing_rule, args):
child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get(
pricing_rule.apply_on
)

apply_on_field = frappe.scrub(pricing_rule.apply_on)

for row in pricing_rule.get(child_doc):
if row.get(apply_on_field) == args.get(apply_on_field):
pricing_rule.uom = row.uom


def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
Expand Down Expand Up @@ -324,6 +336,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa

if isinstance(pricing_rule, str):
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
update_pricing_rule_uom(pricing_rule, args)
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []

if pricing_rule.get("suggestion"):
Expand Down Expand Up @@ -440,12 +453,15 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.currency == args.currency:
pricing_rule_rate = pricing_rule.rate

# TODO https://github.com/frappe/erpnext/pull/23636 solve this in some other way.
if pricing_rule_rate:
is_blank_uom = pricing_rule.get("uom") != args.get("uom")
# Override already set price list rate (from item price)
# if pricing_rule_rate > 0
item_details.update(
{
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
"price_list_rate": pricing_rule_rate
* (args.get("conversion_factor", 1) if is_blank_uom else 1),
}
)
item_details.update({"discount_percentage": 0.0})
Expand Down
115 changes: 115 additions & 0 deletions erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
Expand Up @@ -595,6 +595,121 @@ def test_item_price_with_pricing_rule(self):
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete()

def test_item_price_with_blank_uom_pricing_rule(self):
properties = {
"item_code": "Item Blank UOM",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)

make_item_price("Item Blank UOM", "_Test Price List", 100)

pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item Blank UOM Rule",
"apply_on": "Item Code",
"items": [
{
"item_code": "Item Blank UOM",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()

si = create_sales_invoice(
do_not_save=True, item_code="Item Blank UOM", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()

# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
self.assertEqual(si.items[0].price_list_rate, 1010)
self.assertEqual(si.items[0].rate, 1010)

si.delete()

si = create_sales_invoice(do_not_save=True, item_code="Item Blank UOM", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()

# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)

si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item Blank UOM"}).delete()

item.delete()

def test_item_price_with_selling_uom_pricing_rule(self):
properties = {
"item_code": "Item UOM other than Stock",
"stock_uom": "Nos",
"sales_uom": "Box",
"uoms": [dict(uom="Box", conversion_factor=10)],
}
item = make_item(properties=properties)

make_item_price("Item UOM other than Stock", "_Test Price List", 100)

pricing_rule_record = {
"doctype": "Pricing Rule",
"title": "_Test Item UOM other than Stock Rule",
"apply_on": "Item Code",
"items": [
{
"item_code": "Item UOM other than Stock",
"uom": "Box",
}
],
"selling": 1,
"currency": "INR",
"rate_or_discount": "Rate",
"rate": 101,
"company": "_Test Company",
}
rule = frappe.get_doc(pricing_rule_record)
rule.insert()

si = create_sales_invoice(
do_not_save=True, item_code="Item UOM other than Stock", uom="Box", conversion_factor=10
)
si.selling_price_list = "_Test Price List"
si.save()

# UOM is Box so apply pricing_rule only on Box UOM.
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
self.assertEqual(si.items[0].price_list_rate, 101)
self.assertEqual(si.items[0].rate, 101)

si.delete()

si = create_sales_invoice(do_not_save=True, item_code="Item UOM other than Stock", uom="Nos")
si.selling_price_list = "_Test Price List"
si.save()

# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
self.assertEqual(si.items[0].price_list_rate, 100)
self.assertEqual(si.items[0].rate, 100)

si.delete()
rule.delete()
frappe.get_doc("Item Price", {"item_code": "Item UOM other than Stock"}).delete()

item.delete()

def test_pricing_rule_for_different_currency(self):
make_item("Test Sanitizer Item")

Expand Down
6 changes: 6 additions & 0 deletions erpnext/accounts/doctype/pricing_rule/utils.py
Expand Up @@ -111,6 +111,12 @@ def _get_pricing_rules(apply_on, args, values):
)

if apply_on_field == "item_code":
if args.get("uom", None):
item_conditions += (
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
child_doc=child_doc, item_uom=args.get("uom")
)
)
if "variant_of" not in args:
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")

Expand Down
Expand Up @@ -3320,7 +3320,7 @@ def create_sales_invoice(**args):
"asset": args.asset or None,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
"conversion_factor": 1,
"conversion_factor": args.get("conversion_factor", 1),
"incoming_rate": args.incoming_rate or 0,
"batch_no": args.batch_no or None,
},
Expand Down
3 changes: 3 additions & 0 deletions erpnext/public/js/controllers/transaction.js
Expand Up @@ -426,6 +426,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if(!this.validate_company_and_party()) {
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();
} else {
item.pricing_rules = ''
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_details",
child: item,
Expand Down Expand Up @@ -1045,6 +1046,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
uom(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);
item.pricing_rules = ''
if(item.item_code && item.uom) {
return this.frm.call({
method: "erpnext.stock.get_item_details.get_conversion_factor",
Expand Down Expand Up @@ -1121,6 +1123,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe

qty(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn);
item.pricing_rules = ''
this.conversion_factor(doc, cdt, cdn, true);
this.calculate_stock_uom_rate(doc, cdt, cdn);
this.apply_pricing_rule(item, true);
Expand Down

0 comments on commit ffd82f3

Please sign in to comment.