diff --git a/accounts/doctype/shipping_rule/shipping_rule.py b/accounts/doctype/shipping_rule/shipping_rule.py index 5ed4ed38e0..87148656df 100644 --- a/accounts/doctype/shipping_rule/shipping_rule.py +++ b/accounts/doctype/shipping_rule/shipping_rule.py @@ -3,23 +3,80 @@ from __future__ import unicode_literals import webnotes from webnotes import _, msgprint +from webnotes.utils import flt, fmt_money +from webnotes.model.controller import DocListController +from setup.utils import get_company_currency -class DocType: +class OverlappingConditionError(webnotes.ValidationError): pass +class FromGreaterThanToError(webnotes.ValidationError): pass +class ManyBlankToValuesError(webnotes.ValidationError): pass + +class DocType(DocListController): def __init__(self, d, dl): self.doc, self.doclist = d, dl def validate(self): - self.validate_to_value_of_shipping_rule_conditions() + self.shipping_rule_conditions = self.doclist.get({"parentfield": "shipping_rule_conditions"}) + self.validate_from_to_values() + self.sort_shipping_rule_conditions() self.validate_overlapping_shipping_rule_conditions() + def validate_from_to_values(self): + zero_to_values = [] - def validate_to_value_of_shipping_rule_conditions(self): - """check if more than two or more rows has To Value = 0""" - shipping_rule_conditions_with_0_to_value = self.doclist.get({ - "parentfield": "shipping_rule_conditions", "to_value": ["in", [0, None]]}) - if len(shipping_rule_conditions_with_0_to_value) >= 2: - msgprint(_('''There can only be one shipping rule with 0 or blank value for "To Value"'''), - raise_exception=True) + for d in self.shipping_rule_conditions: + self.round_floats_in(d) + + # values cannot be negative + self.validate_value("from_value", ">=", 0.0, d) + self.validate_value("to_value", ">=", 0.0, d) + + if d.to_value == 0: + zero_to_values.append(d) + elif d.from_value >= d.to_value: + msgprint(_("Error") + ": " + _("Row") + " # %d: " % d.idx + + _("From Value should be less than To Value"), + raise_exception=FromGreaterThanToError) + + # check if more than two or more rows has To Value = 0 + if len(zero_to_values) >= 2: + msgprint(_('''There can only be one Shipping Rule Condition with 0 or blank value for "To Value"'''), + raise_exception=ManyBlankToValuesError) + def sort_shipping_rule_conditions(self): + """Sort Shipping Rule Conditions based on increasing From Value""" + self.shipping_rules_conditions = sorted(self.shipping_rule_conditions, key=lambda d: flt(d.from_value)) + for i, d in enumerate(self.shipping_rule_conditions): + d.idx = i + 1 + def validate_overlapping_shipping_rule_conditions(self): - pass \ No newline at end of file + def overlap_exists_between((x1, x2), (y1, y2)): + """ + (x1, x2) and (y1, y2) are two ranges + if condition x = 100 to 300 + then condition y can only be like 50 to 99 or 301 to 400 + hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) + """ + separate = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) + return (not separate) + + overlaps = [] + for i in xrange(0, len(self.shipping_rule_conditions)): + for j in xrange(i+1, len(self.shipping_rule_conditions)): + d1, d2 = self.shipping_rule_conditions[i], self.shipping_rule_conditions[j] + if d1.fields != d2.fields: + # in our case, to_value can be zero, hence pass the from_value if so + range_a = (d1.from_value, d1.to_value or d1.from_value) + range_b = (d2.from_value, d2.to_value or d2.from_value) + if overlap_exists_between(range_a, range_b): + overlaps.append([d1, d2]) + + if overlaps: + company_currency = get_company_currency(self.doc.company) + msgprint(_("Error") + ": " + _("Overlapping Conditions found between") + ":") + messages = [] + for d1, d2 in overlaps: + messages.append("%s-%s = %s " % (d1.from_value, d1.to_value, fmt_money(d1.shipping_amount, currency=company_currency)) + + _("and") + " %s-%s = %s" % (d2.from_value, d2.to_value, fmt_money(d2.shipping_amount, currency=company_currency))) + + msgprint("\n".join(messages), raise_exception=OverlappingConditionError) \ No newline at end of file diff --git a/accounts/doctype/shipping_rule/test_shipping_rule.py b/accounts/doctype/shipping_rule/test_shipping_rule.py new file mode 100644 index 0000000000..ff217bc5a7 --- /dev/null +++ b/accounts/doctype/shipping_rule/test_shipping_rule.py @@ -0,0 +1,66 @@ +import webnotes +import unittest +from accounts.doctype.shipping_rule.shipping_rule import FromGreaterThanToError, ManyBlankToValuesError, OverlappingConditionError + +class TestShippingRule(unittest.TestCase): + def test_from_greater_than_to(self): + shipping_rule = webnotes.bean(copy=test_records[0]) + shipping_rule.doclist[1].from_value = 101 + self.assertRaises(FromGreaterThanToError, shipping_rule.insert) + + def test_many_zero_to_values(self): + shipping_rule = webnotes.bean(copy=test_records[0]) + shipping_rule.doclist[1].to_value = 0 + self.assertRaises(ManyBlankToValuesError, shipping_rule.insert) + + def test_overlapping_conditions(self): + for range_a, range_b in [ + ((50, 150), (0, 100)), + ((50, 150), (100, 200)), + ((50, 150), (75, 125)), + ((50, 150), (25, 175)), + ((50, 150), (50, 150)), + ]: + shipping_rule = webnotes.bean(copy=test_records[0]) + shipping_rule.doclist[1].from_value = range_a[0] + shipping_rule.doclist[1].to_value = range_a[1] + shipping_rule.doclist[2].from_value = range_b[0] + shipping_rule.doclist[2].to_value = range_b[1] + self.assertRaises(OverlappingConditionError, shipping_rule.insert) + +test_records = [ + [ + { + "doctype": "Shipping Rule", + "calculate_based_on": "Amount", + "company": "_Test Company", + "account": "_Test Account Shipping Charges - _TC", + "cost_center": "_Test Cost Center - _TC" + }, + { + "doctype": "Shipping Rule Condition", + "parentfield": "shipping_rule_conditions", + "from_value": 0, + "to_value": 100, + "shipping_amount": 50.0 + }, + { + "doctype": "Shipping Rule Condition", + "parentfield": "shipping_rule_conditions", + "from_value": 101, + "to_value": 200, + "shipping_amount": 100.0 + }, + { + "doctype": "Shipping Rule Condition", + "parentfield": "shipping_rule_conditions", + "from_value": 201, + "shipping_amount": 0.0 + }, + { + "doctype": "For Territory", + "parentfield": "valid_for_territories", + "territory": "_Test Territory" + } + ] +] \ No newline at end of file