# Commits

Fixed apply_even_split. Closes #1384.

• Participants
• Parent commits 9537667

# File satchmo/apps/product/models.py

`         verbose_name_plural = _("Discounts")`
` `
`     def apply_even_split(cls, discounted, amount):`
`-        lastct = -1`
`-        ct = len(discounted)`
`+        """Splits ``amount`` to the most even values,`
`+        but none of them is greater then the value in the dict ``discounted``.`
`+        > > > cls.apply_even_split({1: Decimal('3.00'), 2: Decimal('8.00'), 3: Decimal('9.00')}, Decimal('10.00'))`
`+        {1: Decimal('3.00'), 2: Decimal('3.50'), 3: Decimal('3.50')}`
`+        """`
`+        context = Context(rounding=ROUND_FLOOR)`
`+        lastct = None`
`+        ct = sentinel = len(discounted)`
`         work = {}`
`-        context = Context(rounding=ROUND_FLOOR)`
`-        if ct > 0:`
`-            split_discount = context.divide(amount, Decimal(ct)).quantize(Decimal("0.01"))`
`-            remainder = amount - context.multiply(split_discount, Decimal(ct))`
`-        else:`
`-            split_discount = remainder = Decimal("0.00")`
`+        applied = delta = Decimal("0.00")`
`+        # "applied" is how much has been applied in the previous round total`
`+        # "delta"   is how much has been applied only for limited values in the previous round`
` `
`-        while ct > 0:`
`-            log.debug("Trying with ct=%i", ct)`
`-            delta = Decimal("0.00")`
`-            applied = Decimal("0.00")`
`+        while ct > 0 and applied < amount and ct != lastct and sentinel:`
`+            split_discount = context.quantize((amount - delta) / ct, Decimal('0.01'))`
`+            remainder = amount - delta - split_discount * ct`
`+            lastct = ct`
`+`
`+            ct = len(discounted)`
`             work = {}`
`-            should_apply_remainder = True`
`+            applied = delta = Decimal("0.00")`
`             for lid, price in discounted.items():`
`-                if should_apply_remainder \`
`-                    and remainder > Decimal('0') \`
`-                    and price > split_discount + remainder:`
`-                    to_apply = split_discount + remainder`
`-                    should_apply_remainder = False`
`-                elif price > split_discount:`
`-                    to_apply = split_discount`
`+                if price > split_discount:`
`+                    if remainder:`
`+                        to_apply = split_discount + Decimal('0.01')`
`+                        remainder -= Decimal('0.01')`
`+                    else:`
`+                        to_apply = split_discount`
`                 else:`
`                     to_apply = price`
`                     delta += price`
`                     ct -= 1`
`-`
`                 work[lid] = to_apply`
`                 applied += to_apply`
`+            sentinel -= 1`
` `
`-            if applied >= amount - Decimal("0.01"):`
`-                ct = 0`
`-`
`-            if ct == lastct:`
`-                ct = 0`
`-            else:`
`-                lastct = ct`
`-`
`-            if ct > 0:`
`-                split_discount = (amount-delta)/ct`
`-`
`+        assert sentinel >= 0, "Infinite loop in 'apply_even_split'"`
`+        assert applied == amount or applied <= amount and applied == delta, "Internal error in 'apply_even_split'"`
`         round_cents(work)`
`         return work`
` `

# File satchmo/apps/product/tests.py

` `
` class CalcFunctionTest(TestCase):`
` `
`+    def assert_apply_even_split(self, input_str, amount_str, expect_str):`
`+        """`
`+        Method which simplifies many similar tests to be written more compact on one line`
`+        Example: the following line does the same as the method ``testEvenSplit1``.`
`+        > > > self.assert_apply_even_split('10 10 10 10', '16', '4.00 4.00 4.00 4.00')`
`+        """`
`+        ddd = input_str.split()`
`+        dd = map(lambda x: Decimal(str(x)).quantize(Decimal("0.01")), ddd)`
`+        d = dict(enumerate(dd))`
`+        amount = Decimal(str(amount_str)).quantize(Decimal("0.01"))`
`+        s = Discount.apply_even_split(d, amount)`
`+        self.assertEqual(s.keys(), d.keys())`
`+        output_str = ' '.join(map(lambda (k, v): str(v), sorted(s.items())))`
`+        self.assertEqual(output_str, expect_str)`
`+`
`+    def testEvenSplit1Duplicate(self):`
`+        """Does the same as the following test, but written more compact on one line""";`
`+        self.assert_apply_even_split('10 10 10 10', '16', '4.00 4.00 4.00 4.00')`
`+`
`+        `
`     def testEvenSplit1(self):`
`         """Simple split test"""`
`         d = {`
`         }`
` `
`         s = Discount.apply_even_split(d, Decimal("10.00"))`
`-        self.assertEqual(s[1], Decimal("3.51"))`
`+        self.assertEqual(s[1], Decimal("3.50"))`
`         self.assertEqual(s[2], Decimal("3.50"))`
`         self.assertEqual(s[3], Decimal("3.00"))`
` `
`         self.assertEqual(s[3], Decimal("1.00"))`
` `
` `
`+    def testEvenSplitUncommonNear(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('6.67 6.67 6.67', '20.00', '6.67 6.67 6.66')`
`+`
`+`
`+    def testEvenSplitUncommon1(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 1.99', '20.00', '6.11 5.80 6.10 1.99')`
`+`
`+    def testEvenSplitUncommon2(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 2.99', '20.00', '5.67 5.67 5.67 2.99')`
`+    `
`+    def testEvenSplitUncommon3(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 1.98', '20.00', '6.11 5.80 6.11 1.98')`
`+    `
`+    def testEvenSplitUncommon4(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 0.98', '20.00', '6.61 5.80 6.61 0.98')`
`+    `
`+    def testEvenSplitUncommon5(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 0.98', '10.00', '3.01 3.01 3.00 0.98')`
`+    `
`+    def testEvenSplitUncommon6(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 3.99', '30.00', '10.11 5.80 10.10 3.99')`
`+    `
`+    def testEvenSplitUncommon7(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 5.80 25.80 3.99', '40.00', '12.90 5.80 17.31 3.99')`
`+    `
`+    def testEvenSplitUncommon8(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('12.90 35.80 25.80 3.99', '40.00', '12.01 12.00 12.00 3.99')`
`+    `
`+    def testEvenSplitUncommon9(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('8.00 15.80 25.80 3.99', '40.00', '8.00 14.01 14.00 3.99')`
`+    `
`+    def testEvenSplitUncommon10(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('8.00 15.80 25.80 13.99', '40.00', '8.00 10.67 10.67 10.66')`
`+    `
`+    def testEvenSplitUncommon11(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('8.00 15.80 25.80 14.00', '40.00', '8.00 10.67 10.67 10.66')`
`+    `
`+    def testEvenSplitUncommon12(self):`
`+        """Simple split test"""`
`+        self.assert_apply_even_split('5.80 25.80 12.90', '20.00', '5.80 7.10 7.10')`
`+`
`+`
` class ProductExportTest(TestCase):`
`     """`
`     Test product export functionality.`