Merge branch 'develop' into fix/github-issue/30339
This commit is contained in:
commit
31f95da947
@ -52,7 +52,10 @@
|
||||
"free_item_rate",
|
||||
"column_break_42",
|
||||
"free_item_uom",
|
||||
"round_free_qty",
|
||||
"is_recursive",
|
||||
"recurse_for",
|
||||
"apply_recursion_over",
|
||||
"section_break_23",
|
||||
"valid_from",
|
||||
"valid_upto",
|
||||
@ -578,12 +581,34 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Naming Series",
|
||||
"options": "PRLE-.####"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "round_free_qty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Free Qty"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_recursive",
|
||||
"description": "Give free item for every N quantity",
|
||||
"fieldname": "recurse_for",
|
||||
"fieldtype": "Float",
|
||||
"label": "Recurse Every (As Per Transaction UOM)",
|
||||
"mandatory_depends_on": "is_recursive"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "is_recursive",
|
||||
"description": "Qty for which recursion isn't applicable.",
|
||||
"fieldname": "apply_recursion_over",
|
||||
"fieldtype": "Float",
|
||||
"label": "Apply Recursion Over (As Per Transaction UOM)"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-16 16:00:38.356266",
|
||||
"modified": "2022-10-13 19:05:35.056304",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -24,6 +24,7 @@ class PricingRule(Document):
|
||||
self.validate_applicable_for_selling_or_buying()
|
||||
self.validate_min_max_amt()
|
||||
self.validate_min_max_qty()
|
||||
self.validate_recursion()
|
||||
self.cleanup_fields_value()
|
||||
self.validate_rate_or_discount()
|
||||
self.validate_max_discount()
|
||||
@ -109,6 +110,18 @@ class PricingRule(Document):
|
||||
if self.min_amt and self.max_amt and flt(self.min_amt) > flt(self.max_amt):
|
||||
throw(_("Min Amt can not be greater than Max Amt"))
|
||||
|
||||
def validate_recursion(self):
|
||||
if self.price_or_product_discount != "Product":
|
||||
return
|
||||
if self.free_item or self.same_item:
|
||||
if flt(self.recurse_for) <= 0:
|
||||
self.recurse_for = 1
|
||||
if self.is_recursive:
|
||||
if flt(self.apply_recursion_over) > flt(self.min_qty):
|
||||
throw(_("Min Qty should be greater than Recurse Over Qty"))
|
||||
if flt(self.apply_recursion_over) < 0:
|
||||
throw(_("Recurse Over Qty cannot be less than 0"))
|
||||
|
||||
def cleanup_fields_value(self):
|
||||
for logic_field in ["apply_on", "applicable_for", "rate_or_discount"]:
|
||||
fieldname = frappe.scrub(self.get(logic_field) or "")
|
||||
|
@ -710,6 +710,132 @@ class TestPricingRule(unittest.TestCase):
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_item_group_price_with_blank_uom_pricing_rule(self):
|
||||
group = frappe.get_doc(doctype="Item Group", item_group_name="_Test Pricing Rule Item Group")
|
||||
group.save()
|
||||
properties = {
|
||||
"item_code": "Item with Group Blank UOM",
|
||||
"item_group": "_Test Pricing Rule Item Group",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item with Group Blank UOM", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item with Group Blank UOM Rule",
|
||||
"apply_on": "Item Group",
|
||||
"item_groups": [
|
||||
{
|
||||
"item_group": "_Test Pricing Rule Item Group",
|
||||
}
|
||||
],
|
||||
"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 with Group 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 with Group 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 with Group Blank UOM"}).delete()
|
||||
item.delete()
|
||||
group.delete()
|
||||
|
||||
def test_item_group_price_with_selling_uom_pricing_rule(self):
|
||||
group = frappe.get_doc(doctype="Item Group", item_group_name="_Test Pricing Rule Item Group UOM")
|
||||
group.save()
|
||||
properties = {
|
||||
"item_code": "Item with Group UOM other than Stock",
|
||||
"item_group": "_Test Pricing Rule Item Group UOM",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item with Group UOM other than Stock", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item with Group UOM other than Stock Rule",
|
||||
"apply_on": "Item Group",
|
||||
"item_groups": [
|
||||
{
|
||||
"item_group": "_Test Pricing Rule Item Group UOM",
|
||||
"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 with Group 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 with Group 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 with Group UOM other than Stock"}).delete()
|
||||
item.delete()
|
||||
group.delete()
|
||||
|
||||
def test_pricing_rule_for_different_currency(self):
|
||||
make_item("Test Sanitizer Item")
|
||||
|
||||
@ -943,6 +1069,45 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.delete()
|
||||
rule.delete()
|
||||
|
||||
def test_pricing_rule_for_product_free_item_rounded_qty_and_recursion(self):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||
test_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule",
|
||||
"apply_on": "Item Code",
|
||||
"currency": "USD",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"rate": 0,
|
||||
"min_qty": 3,
|
||||
"max_qty": 7,
|
||||
"price_or_product_discount": "Product",
|
||||
"same_item": 1,
|
||||
"free_qty": 1,
|
||||
"round_free_qty": 1,
|
||||
"is_recursive": 1,
|
||||
"recurse_for": 2,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
frappe.get_doc(test_record.copy()).insert()
|
||||
|
||||
# With pricing rule
|
||||
so = make_sales_order(item_code="_Test Item", qty=5)
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 2)
|
||||
|
||||
so = make_sales_order(item_code="_Test Item", qty=7)
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||
self.assertEqual(so.items[1].qty, 4)
|
||||
|
||||
|
||||
test_dependencies = ["Campaign"]
|
||||
|
||||
|
@ -127,6 +127,12 @@ def _get_pricing_rules(apply_on, args, values):
|
||||
values["variant_of"] = args.variant_of
|
||||
elif apply_on_field == "item_group":
|
||||
item_conditions = _get_tree_conditions(args, "Item Group", child_doc, False)
|
||||
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")
|
||||
)
|
||||
)
|
||||
|
||||
conditions += get_other_conditions(conditions, values, args)
|
||||
warehouse_conditions = _get_tree_conditions(args, "Warehouse", "`tabPricing Rule`")
|
||||
@ -627,9 +633,13 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
|
||||
qty = pricing_rule.free_qty or 1
|
||||
if pricing_rule.is_recursive:
|
||||
transaction_qty = args.get("qty") if args else doc.total_qty
|
||||
transaction_qty = (
|
||||
args.get("qty") if args else doc.total_qty
|
||||
) - pricing_rule.apply_recursion_over
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty
|
||||
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
||||
if pricing_rule.round_free_qty:
|
||||
qty = round(qty)
|
||||
|
||||
free_item_data_args = {
|
||||
"item_code": free_item,
|
||||
|
@ -221,7 +221,7 @@ class TestAsset(AssetSetup):
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
|
||||
@ -283,7 +283,7 @@ class TestAsset(AssetSetup):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
|
||||
|
@ -1404,7 +1404,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
if (!r.exc && r.message) {
|
||||
me._set_values_for_item_list(r.message);
|
||||
if(item) me.set_gross_profit(item);
|
||||
if(me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
|
||||
if (me.frm.doc.apply_discount_on) me.frm.trigger("apply_discount_on")
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1577,6 +1577,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
for (let key in pr_row) {
|
||||
row_to_modify[key] = pr_row[key];
|
||||
}
|
||||
this.frm.script_manager.copy_from_first_row("items", row_to_modify, ["expense_account", "income_account"]);
|
||||
});
|
||||
|
||||
// free_item_data is a temporary variable
|
||||
|
Loading…
Reference in New Issue
Block a user