Repository: django-oscar/django-oscar

Issue:
Exclusive with Count Condition not work anymore
# Issue Summary

I create 2 offers with count condition, **Both are not exclusive**, After oscar version 2.0, It seem like it just apply only 1 offers not apply both anymore

### Steps to Reproduce
1. Create 2 offers like this 
![Screen Shot 2019-08-29 at 17 02 19](https://user-images.githubusercontent.com/8855366/63931190-01b7f080-ca7f-11e9-8e34-d93f01c3d102.png)

2. Only 1 offer apply
![Screen Shot 2019-08-29 at 17 06 09](https://user-images.githubusercontent.com/8855366/63931399-67a47800-ca7f-11e9-8989-988bd9cf5b05.png)

**Expected**
1. Should apply both offers if it does not exclusive.?

Note
I try to dig into the issue and  compare the load, I found out that, in condition.py they are some changed about is_satisfied like this image below

<img width="1606" alt="Screen Shot 2019-08-29 at 17 08 11" src="https://user-images.githubusercontent.com/8855366/63931528-a6d2c900-ca7f-11e9-8f08-24e6a7966516.png">

When I reverse code back it work like a charm, Any idea why you guy change that?

### Technical details

* Python version: 3.6.8
* Django version: 1.11.5
* Oscar version: 2.0.1.



Discussion:
Indeed, this was changed in https://github.com/django-oscar/django-oscar/commit/c1c61ef5c5112068fbaeadf5714423becec8ba25#diff-008132744b4a8f62da8f13ae6286db70R49-R50, but only for `CountCondition`, not for `CoverageCondition` and `ValueCondition`, so I think this was an error.
Or the reason could also be in the `Condition.can_apply_condition` method. As the author of the change, could you please suggest @pjstevns what was the motivation of the change?
Are there any news on this issue? Or could I help with something. 
@bruecksen it needs someone to spend a bit of time working out a fix - if you have time to do so that would be appreciated.

Resolving patch:
diff --git a/src/oscar/apps/basket/utils.py b/src/oscar/apps/basket/utils.py
--- a/src/oscar/apps/basket/utils.py
+++ b/src/oscar/apps/basket/utils.py
@@ -143,7 +143,7 @@ def consumed(self, offer=None):
     def consumers(self):
         return [x for x in self._offers.values() if self.consumed(x)]
 
-    def available(self, offer=None) -> int: # noqa (too complex (11))
+    def available(self, offer=None) -> int:
         """
         check how many items are available for offer
 
@@ -186,8 +186,4 @@ def available(self, offer=None) -> int: # noqa (too complex (11))
                 if check and offer not in x.combined_offers:
                     return 0
 
-            # respect max_affected_items
-            if offer.benefit.max_affected_items:
-                max_affected_items = min(offer.benefit.max_affected_items, max_affected_items)
-
         return max_affected_items - self.consumed(offer)
diff --git a/src/oscar/apps/offer/benefits.py b/src/oscar/apps/offer/benefits.py
--- a/src/oscar/apps/offer/benefits.py
+++ b/src/oscar/apps/offer/benefits.py
@@ -67,6 +67,7 @@ def apply(self, basket, condition, offer, discount_percent=None,
         max_affected_items = self._effective_max_affected_items()
         affected_lines = []
         for price, line in line_tuples:
+            affected_items += line.quantity_with_offer_discount(offer)
             if affected_items >= max_affected_items:
                 break
             if discount_amount_available == 0:
diff --git a/src/oscar/apps/offer/conditions.py b/src/oscar/apps/offer/conditions.py
--- a/src/oscar/apps/offer/conditions.py
+++ b/src/oscar/apps/offer/conditions.py
@@ -47,7 +47,7 @@ def is_satisfied(self, offer, basket):
         num_matches = 0
         for line in basket.all_lines():
             if self.can_apply_condition(line):
-                num_matches += line.quantity_without_offer_discount(None)
+                num_matches += line.quantity_without_offer_discount(offer)
             if num_matches >= self.value:
                 return True
         return False


Test patch:
diff --git a/tests/integration/basket/test_utils.py b/tests/integration/basket/test_utils.py
--- a/tests/integration/basket/test_utils.py
+++ b/tests/integration/basket/test_utils.py
@@ -76,7 +76,7 @@ def test_available_with_offer(self):
         offer1 = ConditionalOfferFactory(name='offer1', benefit=benefit)
         lines = basket.all_lines()
         assert lines[0].consumer.available(offer1) == 1
-        assert lines[1].consumer.available(offer1) == 5
+        assert lines[1].consumer.available(offer1) == 10
 
     def test_consumed_with_offer(self, filled_basket):
         offer1 = ConditionalOfferFactory(name='offer1')
diff --git a/tests/integration/offer/test_condition.py b/tests/integration/offer/test_condition.py
--- a/tests/integration/offer/test_condition.py
+++ b/tests/integration/offer/test_condition.py
@@ -3,13 +3,17 @@
 
 import pytest
 from django.test import TestCase
+from django.utils.timezone import now
 
 from oscar.apps.basket.models import Basket
-from oscar.apps.offer import custom, models
+from oscar.apps.offer import applicator, custom, models
+from oscar.core.loading import get_class
 from oscar.test import factories
 from oscar.test.basket import add_product
 from tests._site.model_tests_app.models import BasketOwnerCalledBarry
 
+Selector = get_class('partner.strategy', 'Selector')
+
 
 @pytest.fixture
 def products_some():
@@ -329,3 +333,47 @@ def test_is_not_satisfied_by_non_match(self):
     def test_is_satisfied_by_match(self):
         self.basket.owner = factories.UserFactory(first_name="Barry")
         assert self.offer.is_condition_satisfied(self.basket)
+
+
+class TestOffersWithCountCondition(TestCase):
+
+    def setUp(self):
+        super().setUp()
+
+        self.basket = factories.create_basket(empty=True)
+
+        # Create range and add one product to it.
+        rng = factories.RangeFactory(name='All products', includes_all_products=True)
+        self.product = factories.ProductFactory()
+        rng.add_product(self.product)
+
+        # Create a non-exclusive offer #1.
+        condition1 = factories.ConditionFactory(range=rng, value=D('1'))
+        benefit1 = factories.BenefitFactory(range=rng, value=D('10'))
+        self.offer1 = factories.ConditionalOfferFactory(
+            condition=condition1, benefit=benefit1, start_datetime=now(),
+            name='Test offer #1', exclusive=False,
+        )
+
+        # Create a non-exclusive offer #2.
+        condition2 = factories.ConditionFactory(range=rng, value=D('1'))
+        benefit2 = factories.BenefitFactory(range=rng, value=D('5'))
+        self.offer2 = factories.ConditionalOfferFactory(
+            condition=condition2, benefit=benefit2, start_datetime=now(),
+            name='Test offer #2', exclusive=False,
+        )
+
+    def add_product(self):
+        self.basket.add_product(self.product)
+        self.basket.strategy = Selector().strategy()
+        applicator.Applicator().apply(self.basket)
+
+    def assertOffersApplied(self, offers):
+        applied_offers = self.basket.applied_offers()
+        self.assertEqual(len(offers), len(applied_offers))
+        for offer in offers:
+            self.assertIn(offer.id, applied_offers, msg=offer)
+
+    def test_both_non_exclusive_offers_are_applied(self):
+        self.add_product()
+        self.assertOffersApplied([self.offer1, self.offer2])