Merge branch 'develop' into prod-plan-status

This commit is contained in:
Marica 2020-10-30 12:33:27 +05:30 committed by GitHub
commit b05318aa51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 40 deletions

View File

@ -504,10 +504,10 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules", "depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
"fieldname": "apply_discount_on_rate", "fieldname": "apply_discount_on_rate",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Apply Discount on Rate" "label": "Apply Discount on Discounted Rate"
}, },
{ {
"default": "0", "default": "0",
@ -563,7 +563,7 @@
"icon": "fa fa-gift", "icon": "fa fa-gift",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2020-08-26 12:24:44.740734", "modified": "2020-10-28 16:53:14.416172",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Pricing Rule", "name": "Pricing Rule",

View File

@ -60,6 +60,15 @@ class PricingRule(Document):
if self.price_or_product_discount == 'Price' and not self.rate_or_discount: if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
if self.apply_discount_on_rate:
if not self.priority:
throw(_("As the field {0} is enabled, the field {1} is mandatory.")
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
if self.priority and cint(self.priority) == 1:
throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.")
.format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")))
def validate_applicable_for_selling_or_buying(self): def validate_applicable_for_selling_or_buying(self):
if not self.selling and not self.buying: if not self.selling and not self.buying:
throw(_("Atleast one of the Selling or Buying must be selected")) throw(_("Atleast one of the Selling or Buying must be selected"))
@ -226,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
item_details = frappe._dict({ item_details = frappe._dict({
"doctype": args.doctype, "doctype": args.doctype,
"has_margin": False,
"name": args.name, "name": args.name,
"parent": args.parent, "parent": args.parent,
"parenttype": args.parenttype, "parenttype": args.parenttype,
"child_docname": args.get('child_docname'), "child_docname": args.get('child_docname')
"discount_percentage_on_rate": [],
"discount_amount_on_rate": []
}) })
if args.ignore_pricing_rule or not args.item_code: if args.ignore_pricing_rule or not args.item_code:
@ -279,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
else: else:
get_product_discount_rule(pricing_rule, item_details, args, doc) get_product_discount_rule(pricing_rule, item_details, args, doc)
if not item_details.get("has_margin"):
item_details.margin_type = None
item_details.margin_rate_or_amount = 0.0
item_details.has_pricing_rule = 1 item_details.has_pricing_rule = 1
item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules]) item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
@ -330,13 +342,11 @@ def get_pricing_rule_details(args, pricing_rule):
def apply_price_discount_rule(pricing_rule, item_details, args): def apply_price_discount_rule(pricing_rule, item_details, args):
item_details.pricing_rule_for = pricing_rule.rate_or_discount item_details.pricing_rule_for = pricing_rule.rate_or_discount
if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency) if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
or (pricing_rule.margin_type == 'Percentage')): or (pricing_rule.margin_type == 'Percentage')):
item_details.margin_type = pricing_rule.margin_type item_details.margin_type = pricing_rule.margin_type
item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
else: item_details.has_margin = True
item_details.margin_type = None
item_details.margin_rate_or_amount = 0.0
if pricing_rule.rate_or_discount == 'Rate': if pricing_rule.rate_or_discount == 'Rate':
pricing_rule_rate = 0.0 pricing_rule_rate = 0.0
@ -351,9 +361,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.rate_or_discount != apply_on: continue if pricing_rule.rate_or_discount != apply_on: continue
field = frappe.scrub(apply_on) field = frappe.scrub(apply_on)
if pricing_rule.apply_discount_on_rate: if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
discount_field = "{0}_on_rate".format(field) # Apply discount on discounted rate
item_details[discount_field].append(pricing_rule.get(field, 0)) item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100))
else: else:
if field not in item_details: if field not in item_details:
item_details.setdefault(field, 0) item_details.setdefault(field, 0)
@ -361,14 +371,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
item_details[field] += (pricing_rule.get(field, 0) item_details[field] += (pricing_rule.get(field, 0)
if pricing_rule else args.get(field, 0)) if pricing_rule else args.get(field, 0))
def set_discount_amount(rate, item_details):
for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']:
for d in item_details.get(field):
dis_amount = (rate * d / 100
if field == 'discount_percentage_on_rate' else d)
rate -= dis_amount
item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules, from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
get_pricing_rule_items) get_pricing_rule_items)

View File

@ -385,7 +385,7 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db() so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1) self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2") self.assertEqual(so.items[1].item_code, "_Test Item 2")
def test_cumulative_pricing_rule(self): def test_cumulative_pricing_rule(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule') frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
test_record = { test_record = {
@ -429,34 +429,61 @@ class TestPricingRule(unittest.TestCase):
details = get_item_details(args) details = get_item_details(args)
self.assertTrue(details) self.assertTrue(details)
def test_pricing_rule_for_condition(self): def test_pricing_rule_for_condition(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(selling=1, margin_type="Percentage", \ make_pricing_rule(selling=1, margin_type="Percentage", \
condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10) condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10)
# Incorrect Customer and Correct is_return value # Incorrect Customer and Correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0) si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0)
si.items[0].price_list_rate = 1000 si.items[0].price_list_rate = 1000
si.submit() si.submit()
item = si.items[0] item = si.items[0]
self.assertEquals(item.rate, 100) self.assertEquals(item.rate, 100)
# Correct Customer and Incorrect is_return value # Correct Customer and Incorrect is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1) si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
si.items[0].price_list_rate = 1000 si.items[0].price_list_rate = 1000
si.submit() si.submit()
item = si.items[0] item = si.items[0]
self.assertEquals(item.rate, 100) self.assertEquals(item.rate, 100)
# Correct Customer and correct is_return value # Correct Customer and correct is_return value
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0) si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
si.items[0].price_list_rate = 1000 si.items[0].price_list_rate = 1000
si.submit() si.submit()
item = si.items[0] item = si.items[0]
self.assertEquals(item.rate, 900) self.assertEquals(item.rate, 900)
def test_multiple_pricing_rules(self):
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
title="_Test Pricing Rule 1")
make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2,
apply_multiple_pricing_rules=1)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 30)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
title="_Test Pricing Rule 1")
make_pricing_rule(discount_percentage=10, selling=1, priority=2,
apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
self.assertEqual(si.items[0].discount_percentage, 28)
si.delete()
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def make_pricing_rule(**args): def make_pricing_rule(**args):
args = frappe._dict(args) args = frappe._dict(args)
@ -468,6 +495,7 @@ def make_pricing_rule(**args):
"applicable_for": args.applicable_for, "applicable_for": args.applicable_for,
"selling": args.selling or 0, "selling": args.selling or 0,
"currency": "USD", "currency": "USD",
"apply_discount_on_rate": args.apply_discount_on_rate or 0,
"buying": args.buying or 0, "buying": args.buying or 0,
"min_qty": args.min_qty or 0.0, "min_qty": args.min_qty or 0.0,
"max_qty": args.max_qty or 0.0, "max_qty": args.max_qty or 0.0,
@ -476,9 +504,13 @@ def make_pricing_rule(**args):
"rate": args.rate or 0.0, "rate": args.rate or 0.0,
"margin_type": args.margin_type, "margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0, "margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or '' "condition": args.condition or '',
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
}) })
if args.get("priority"):
doc.priority = args.get("priority")
apply_on = doc.apply_on.replace(' ', '_').lower() apply_on = doc.apply_on.replace(' ', '_').lower()
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'} child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
doc.append(child_table.get(doc.apply_on), { doc.append(child_table.get(doc.apply_on), {

View File

@ -14,9 +14,8 @@ import frappe
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
from frappe import _, throw from frappe import _, bold
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
class MultiplePricingRuleConflict(frappe.ValidationError): pass class MultiplePricingRuleConflict(frappe.ValidationError): pass
@ -42,6 +41,7 @@ def get_pricing_rules(args, doc=None):
if not pricing_rules: return [] if not pricing_rules: return []
if apply_multiple_pricing_rules(pricing_rules): if apply_multiple_pricing_rules(pricing_rules):
pricing_rules = sorted_by_priority(pricing_rules)
for pricing_rule in pricing_rules: for pricing_rule in pricing_rules:
pricing_rule = filter_pricing_rules(args, pricing_rule, doc) pricing_rule = filter_pricing_rules(args, pricing_rule, doc)
if pricing_rule: if pricing_rule:
@ -53,6 +53,20 @@ def get_pricing_rules(args, doc=None):
return rules return rules
def sorted_by_priority(pricing_rules):
# If more than one pricing rules, then sort by priority
pricing_rules_list = []
pricing_rule_dict = {}
for pricing_rule in pricing_rules:
if not pricing_rule.get("priority"): continue
pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule)
for key in sorted(pricing_rule_dict):
pricing_rules_list.append(pricing_rule_dict.get(key))
return pricing_rules_list or pricing_rules
def filter_pricing_rule_based_on_condition(pricing_rules, doc=None): def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
filtered_pricing_rules = [] filtered_pricing_rules = []
if doc: if doc:
@ -284,12 +298,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
fieldname = field fieldname = field
if fieldname: if fieldname:
msg = _("""If you {0} {1} quantities of the item <b>{2}</b>, the scheme <b>{3}</b> msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.")
will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description) .format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)))
if fieldname in ['min_amt', 'max_amt']: if fieldname in ['min_amt', 'max_amt']:
msg = _("""If you {0} {1} worth item <b>{2}</b>, the scheme <b>{3}</b> will be applied on the item. msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.")
""").format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description) .format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")),
bold(item_code), bold(args.rule_description)))
frappe.msgprint(msg) frappe.msgprint(msg)

View File

@ -263,6 +263,7 @@ class AccountsController(TransactionBase):
if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"): if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": parent_dict.get("party_name")}) parent_dict.update({"customer": parent_dict.get("party_name")})
self.pricing_rules = []
for item in self.get("items"): for item in self.get("items"):
if item.get("item_code"): if item.get("item_code"):
args = parent_dict.copy() args = parent_dict.copy()
@ -301,6 +302,7 @@ class AccountsController(TransactionBase):
if ret.get("pricing_rules"): if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret) self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
if self.doctype == "Purchase Invoice": if self.doctype == "Purchase Invoice":
self.set_expense_account(for_validate) self.set_expense_account(for_validate)
@ -322,6 +324,9 @@ class AccountsController(TransactionBase):
if item.get('discount_amount'): if item.get('discount_amount'):
item.rate = item.price_list_rate - item.discount_amount item.rate = item.price_list_rate - item.discount_amount
if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"):
item.rate = pricing_rule_args.get("rate")
elif pricing_rule_args.get('free_item_data'): elif pricing_rule_args.get('free_item_data'):
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
@ -335,6 +340,18 @@ class AccountsController(TransactionBase):
frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}") frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}")
.format(item.idx, frappe.bold(title), frappe.bold(item.item_code))) .format(item.idx, frappe.bold(title), frappe.bold(item.item_code)))
def set_pricing_rule_details(self, item_row, args):
pricing_rules = get_applied_pricing_rules(args.get("pricing_rules"))
if not pricing_rules: return
for pricing_rule in pricing_rules:
self.append("pricing_rules", {
"pricing_rule": pricing_rule,
"item_code": item_row.item_code,
"child_docname": item_row.name,
"rule_applied": True
})
def set_taxes(self): def set_taxes(self):
if not self.meta.get_field("taxes"): if not self.meta.get_field("taxes"):
return return

View File

@ -608,16 +608,19 @@ class calculate_taxes_and_totals(object):
base_rate_with_margin = 0.0 base_rate_with_margin = 0.0
if item.price_list_rate: if item.price_list_rate:
if item.pricing_rules and not self.doc.ignore_pricing_rule: if item.pricing_rules and not self.doc.ignore_pricing_rule:
has_margin = False
for d in get_applied_pricing_rules(item.pricing_rules): for d in get_applied_pricing_rules(item.pricing_rules):
pricing_rule = frappe.get_cached_doc('Pricing Rule', d) pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\
or (pricing_rule.margin_type == 'Percentage'): or (pricing_rule.margin_type == 'Percentage'):
item.margin_type = pricing_rule.margin_type item.margin_type = pricing_rule.margin_type
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
else: has_margin = True
item.margin_type = None
item.margin_rate_or_amount = 0.0 if not has_margin:
item.margin_type = None
item.margin_rate_or_amount = 0.0
if item.margin_type and item.margin_rate_or_amount: if item.margin_type and item.margin_rate_or_amount:
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100 margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100