Commits

Hynek Cernoch committed 962adb2

Fixed apply_even_split. Closes #1384.

  • Participants
  • Parent commits 9537667

Comments (0)

Files changed (2)

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.