Browse Source

When there are product discountable true and variant discountable false in cart in the same time, discount is calculated for variant which is incorrect. (#4102)

* fix use get_is_discountable instead is_discountable attribute from product

* add test case

* add check discount percentage in test

* Fix test case

* remove file setting

* basket add product in test

* Make the test pass

---------

Co-authored-by: Kaew <ajchariya.ung@gmail.com>
master
Voxin Muyli 2 years ago
parent
commit
c862b4c8d7
No account linked to committer's email address

+ 6
- 6
src/oscar/apps/catalogue/abstract_models.py View File

24
 from django.utils.translation import pgettext, pgettext_lazy
24
 from django.utils.translation import pgettext, pgettext_lazy
25
 from treebeard.mp_tree import MP_Node
25
 from treebeard.mp_tree import MP_Node
26
 
26
 
27
+from oscar.core.decorators import deprecated
27
 from oscar.core.loading import get_class, get_classes, get_model
28
 from oscar.core.loading import get_class, get_classes, get_model
28
 from oscar.core.utils import slugify
29
 from oscar.core.utils import slugify
29
 from oscar.core.validators import non_python_keyword
30
 from oscar.core.validators import non_python_keyword
647
             return self.product_class
648
             return self.product_class
648
     get_product_class.short_description = _("Product class")
649
     get_product_class.short_description = _("Product class")
649
 
650
 
651
+    @deprecated
650
     def get_is_discountable(self):
652
     def get_is_discountable(self):
651
         """
653
         """
652
-        At the moment, :py:attr:`.is_discountable` can't be set individually for child
653
-        products; they inherit it from their parent.
654
+        It used to be that, :py:attr:`.is_discountable` couldn't be set individually for child
655
+        products; so they had to inherit it from their parent. This is nolonger the case because
656
+        ranges can include child products as well. That make this method useless.
654
         """
657
         """
655
-        if self.is_child:
656
-            return self.parent.is_discountable
657
-        else:
658
-            return self.is_discountable
658
+        return self.is_discountable
659
 
659
 
660
     def get_categories(self):
660
     def get_categories(self):
661
         """
661
         """

+ 1
- 4
src/oscar/apps/offer/abstract_models.py View File

805
             return False
805
             return False
806
         product = line.product
806
         product = line.product
807
         return (self.range.contains_product(product)
807
         return (self.range.contains_product(product)
808
-                and product.get_is_discountable())
808
+                and product.is_discountable)
809
 
809
 
810
     def get_applicable_lines(self, offer, basket, most_expensive_first=True):
810
     def get_applicable_lines(self, offer, basket, most_expensive_first=True):
811
         """
811
         """
829
 class AbstractRange(models.Model):
829
 class AbstractRange(models.Model):
830
     """
830
     """
831
     Represents a range of products that can be used within an offer.
831
     Represents a range of products that can be used within an offer.
832
-
833
-    Ranges only support adding parent or stand-alone products. Offers will
834
-    consider child products automatically.
835
     """
832
     """
836
     name = models.CharField(_("Name"), max_length=128, unique=True)
833
     name = models.CharField(_("Name"), max_length=128, unique=True)
837
     slug = fields.AutoSlugField(
834
     slug = fields.AutoSlugField(

+ 0
- 1
tests/integration/catalogue/test_product.py View File

106
             is_discountable=True)
106
             is_discountable=True)
107
         self.assertEqual("Parent product", p.get_title())
107
         self.assertEqual("Parent product", p.get_title())
108
         self.assertEqual("Clothing", p.get_product_class().name)
108
         self.assertEqual("Clothing", p.get_product_class().name)
109
-        self.assertEqual(False, p.get_is_discountable())
110
 
109
 
111
     def test_child_products_are_not_part_of_browsable_set(self):
110
     def test_child_products_are_not_part_of_browsable_set(self):
112
         Product.objects.create(
111
         Product.objects.create(

+ 60
- 0
tests/integration/offer/test_absolute_benefit.py View File

386
         assert line.line_price_excl_tax_incl_discounts == D(0)
386
         assert line.line_price_excl_tax_incl_discounts == D(0)
387
         assert line.line_price_incl_tax_incl_discounts == D(0)
387
         assert line.line_price_incl_tax_incl_discounts == D(0)
388
         assert basket.total_incl_tax == 0
388
         assert basket.total_incl_tax == 0
389
+
390
+    def test_is_discountable_works_on_child_level(self):
391
+        rng = factories.RangeFactory(includes_all_products=True, name="klaazien")
392
+        benefit = factories.BenefitFactory(
393
+            range=rng, type=models.Benefit.PERCENTAGE, value=5, max_affected_items=100
394
+        )
395
+        condition = models.ValueCondition.objects.create(
396
+            range=rng,
397
+            type=models.Condition.COUNT,
398
+            value=1
399
+        )
400
+
401
+        factories.ConditionalOfferFactory(
402
+            priority=99999,
403
+            exclusive=False,
404
+            condition=condition,
405
+            benefit=benefit
406
+        )
407
+
408
+        basket = factories.create_basket(empty=True)
409
+
410
+        prod1 = factories.create_product(title="Gert is friends with Berrie", is_discountable=True, price=100)
411
+
412
+        parent_discountable_product = factories.create_product(structure='parent', is_discountable=True)
413
+        child = factories.create_product(
414
+            title="Undiscountable variant",
415
+            structure='child',
416
+            parent=parent_discountable_product,
417
+            is_discountable=False,
418
+            price=100
419
+        )
420
+
421
+        parent_product = factories.create_product(structure='parent', is_discountable=False)
422
+        child_discountable = factories.create_product(
423
+            title="Discountable variant ", structure='child', parent=parent_product, is_discountable=True, price=200)
424
+
425
+        basket.add_product(prod1, quantity=1)
426
+        basket.add_product(child, quantity=2)
427
+        basket.add_product(child_discountable, quantity=3)
428
+
429
+        Applicator().apply(basket)
430
+        line = basket.all_lines()
431
+        product_actual = benefit.can_apply_benefit(line[0])
432
+        assert product_actual
433
+        assert prod1.is_discountable
434
+        assert line[0].has_discount
435
+        assert line[0].discount_value == D(5)
436
+
437
+        variant_actual = benefit.can_apply_benefit(line[1])
438
+        assert not variant_actual
439
+        assert parent_discountable_product.is_discountable
440
+        assert not child.is_discountable
441
+        assert line[1].discount_value == D(0)
442
+
443
+        variant_discountable_actual = benefit.can_apply_benefit(line[2])
444
+        assert variant_discountable_actual
445
+        assert not parent_product.is_discountable
446
+        assert child_discountable.is_discountable
447
+        assert line[2].has_discount
448
+        assert line[2].discount_value == D(30)

Loading…
Cancel
Save