# pylint: disable=redefined-outer-name import pytest from oscar.apps.offer import models from oscar.apps.offer.applicator import Applicator from oscar.test.factories import ( BasketFactory, ConditionalOfferFactory, ProductFactory, VoucherFactory, ) @pytest.fixture def filled_basket(): basket = BasketFactory() product1 = ProductFactory() product2 = ProductFactory() basket.add_product(product1, quantity=10) basket.add_product(product2, quantity=20) return basket @pytest.fixture def single_offer(): return ConditionalOfferFactory( condition__range__includes_all_products=True, condition__value=1, benefit__range__includes_all_products=True, benefit__max_affected_items=1, name="offer1", exclusive=False, ) @pytest.fixture def multi_offers(): offer1 = ConditionalOfferFactory( condition__range__includes_all_products=True, benefit__range__includes_all_products=True, name="offer1", exclusive=False, ) offer2 = ConditionalOfferFactory( condition__range__includes_all_products=True, benefit__range__includes_all_products=True, name="offer2", exclusive=False, ) offer3 = ConditionalOfferFactory( condition__range__includes_all_products=True, benefit__range__includes_all_products=True, name="offer3", exclusive=False, ) return offer1, offer2, offer3 @pytest.mark.django_db class TestLineOfferConsumer: def test_consumed_no_offer(self, filled_basket): for line in filled_basket.all_lines(): assert line.discounts.num_consumed() == 0 def test_available_with_offer(self): basket = BasketFactory() product1 = ProductFactory() product2 = ProductFactory() basket.add_product(product1, quantity=1) basket.add_product(product2, quantity=10) benefit = models.Benefit( type=models.Benefit.PERCENTAGE, value=10, max_affected_items=5, ) benefit.save() offer1 = ConditionalOfferFactory(name="offer1", benefit=benefit) lines = basket.all_lines() assert lines[0].discounts.available(offer1) == 1 assert lines[1].discounts.available(offer1) == 10 def test_consumed_with_offer(self, filled_basket): offer1 = ConditionalOfferFactory(name="offer1") offer2 = ConditionalOfferFactory(name="offer2") offer1.exclusive = False offer2.exclusive = False for line in filled_basket.all_lines(): assert line.discounts.num_consumed(offer1) == 0 assert line.discounts.num_consumed(offer2) == 0 line1 = filled_basket.all_lines()[0] line2 = filled_basket.all_lines()[1] line1.discounts.consume(1, offer1) assert line1.discounts.num_consumed() == 1 assert line1.discounts.num_consumed(offer1) == 1 assert line1.discounts.num_consumed(offer2) == 0 line1.discounts.consume(9, offer1) assert line1.discounts.num_consumed() == line1.quantity assert line1.discounts.num_consumed(offer1) == line1.quantity assert line1.discounts.num_consumed(offer2) == 0 line1.discounts.consume(99, offer1) assert line1.discounts.num_consumed(offer1) == line1.quantity assert line1.discounts.num_consumed(offer2) == 0 line1.discounts.consume(1, offer2) line2.discounts.consume(1, offer2) assert line1.discounts.num_consumed(offer2) == 1 assert line2.discounts.num_consumed(offer2) == 1 def test_consume(self, filled_basket): line = filled_basket.all_lines()[0] line.consume(1) assert line.quantity_with_discount == 1 line.consume(99) assert line.quantity_with_discount == 10 def test_consumed_with_exclusive_offer_1(self, filled_basket): offer1 = ConditionalOfferFactory(name="offer1") offer2 = ConditionalOfferFactory(name="offer2") offer3 = ConditionalOfferFactory(name="offer3") offer1.exclusive = True offer2.exclusive = False offer3.exclusive = False for line in filled_basket.all_lines(): assert line.discounts.num_consumed(offer1) == 0 assert line.discounts.num_consumed(offer2) == 0 line1, line2 = list(filled_basket.all_lines()) # exclusive offer consumes one item on line1 line1.discounts.consume(1, offer1) # offer1 is exclusive so that blocks other offers assert line1.is_available_for_offer_discount(offer2) is False line1.discounts.consume(99, offer1) # ran out of room for offer1 assert line1.is_available_for_offer_discount(offer1) is False # offer2 was never an option assert line1.is_available_for_offer_discount(offer2) is False # exclusivity is per line so line2 is available for offer2 line2.discounts.consume(1, offer2) # nope: exclusive and non-exclusive don't mix assert line2.is_available_for_offer_discount(offer1) is False line2.discounts.consume(99, offer2) # ran out of room for offer2 assert line2.is_available_for_offer_discount(offer1) is False # but still room for offer3! assert line2.is_available_for_offer_discount(offer3) is True def test_consumed_with_exclusive_offer_2(self, filled_basket): offer1 = ConditionalOfferFactory(name="offer1") offer2 = ConditionalOfferFactory(name="offer2") offer3 = ConditionalOfferFactory(name="offer3") offer1.exclusive = True offer2.exclusive = False offer3.exclusive = False for line in filled_basket.all_lines(): assert line.discounts.num_consumed(offer1) == 0 assert line.discounts.num_consumed(offer2) == 0 line1, line2 = list(filled_basket.all_lines()) # exclusive offer consumes one item on line1 line1.discounts.consume(1, offer1) remaining1 = line1.quantity - 1 assert line1.quantity_with_offer_discount(offer1) == 1 assert line1.quantity_with_offer_discount(offer2) == 0 assert line1.quantity_with_offer_discount(offer3) == 0 assert line1.quantity_without_offer_discount(offer1) == remaining1 assert line1.quantity_without_offer_discount(offer2) == 0 assert line1.quantity_without_offer_discount(offer3) == 0 # exclusive offer consumes all items on line1 line1.discounts.consume(remaining1, offer1) assert line1.quantity_with_offer_discount(offer1) == line1.quantity assert line1.quantity_with_offer_discount(offer2) == 0 assert line1.quantity_with_offer_discount(offer3) == 0 assert line1.quantity_without_offer_discount(offer1) == 0 assert line1.quantity_without_offer_discount(offer2) == 0 assert line1.quantity_without_offer_discount(offer3) == 0 # non-exclusive offer consumes one item on line2 line2.discounts.consume(1, offer2) remaining2 = line2.quantity - 1 assert line2.quantity_with_offer_discount(offer1) == 0 assert line2.quantity_with_offer_discount(offer2) == 1 assert line2.quantity_with_offer_discount(offer3) == 0 assert line2.quantity_without_offer_discount(offer1) == 0 assert line2.quantity_without_offer_discount(offer2) == remaining2 assert line2.quantity_without_offer_discount(offer3) == line2.quantity # non-exclusive offer consumes all items on line2 line2.discounts.consume(remaining2, offer2) assert line2.quantity_with_offer_discount(offer1) == 0 assert line2.quantity_with_offer_discount(offer2) == line2.quantity assert line2.quantity_with_offer_discount(offer3) == 0 assert line2.quantity_without_offer_discount(offer1) == 0 assert line2.quantity_without_offer_discount(offer2) == 0 assert line2.quantity_without_offer_discount(offer3) == line2.quantity # pylint: disable=unused-argument def test_consumed_by_application(self, filled_basket, single_offer): basket = filled_basket Applicator().apply(basket) assert len(basket.offer_applications.offer_discounts) == 1 assert [x.discounts.num_consumed() for x in basket.all_lines()] == [1, 0] def test_apply_multiple_vouchers(self, filled_basket): offer1 = ConditionalOfferFactory( condition__range__includes_all_products=True, benefit__range__includes_all_products=True, name="offer1", exclusive=True, ) voucher1 = VoucherFactory(name="voucher1", code="VOUCHER1") voucher1.offers.add(offer1) offer2 = ConditionalOfferFactory( condition__range__includes_all_products=True, benefit__range__includes_all_products=True, name="offer2", exclusive=True, ) voucher2 = VoucherFactory(name="voucher2", code="VOUCHER2") voucher2.offers.add(offer2) offer1.exclusive = True offer2.exclusive = True assert len(filled_basket.offer_applications) == 0 Applicator().apply_offers(basket=filled_basket, offers=[offer2, offer1]) filled_basket.refresh_from_db() # Only one should be applied because they're both exclusive. assert len(filled_basket.offer_applications) == 1 def test_consumed_with_combined_offer(self, filled_basket): offer1 = ConditionalOfferFactory(name="offer1") offer2 = ConditionalOfferFactory(name="offer2") offer3 = ConditionalOfferFactory(name="offer3") offer4 = ConditionalOfferFactory(name="offer4") offer1.exclusive = True offer2.exclusive = False offer3.exclusive = False offer4.exclusive = False offer2.combinations.add(offer3) assert offer3 in offer2.combined_offers assert offer2 in offer3.combined_offers for line in filled_basket.all_lines(): assert line.discounts.num_consumed(offer1) == 0 assert line.discounts.num_consumed(offer2) == 0 assert line.discounts.num_consumed(offer3) == 0 line1 = filled_basket.all_lines()[0] # combinable offer consumes one item of line1 line1.discounts.consume(1, offer2) remaining1 = line1.quantity - 1 assert line1.quantity_with_offer_discount(offer1) == 0 assert line1.quantity_with_offer_discount(offer2) == 1 assert line1.quantity_with_offer_discount(offer3) == 0 assert line1.quantity_with_offer_discount(offer4) == 0 assert line1.quantity_without_offer_discount(offer1) == 0 assert line1.quantity_without_offer_discount(offer2) == remaining1 assert line1.quantity_without_offer_discount(offer3) == line1.quantity assert line1.quantity_without_offer_discount(offer4) == 0 # combinable offer consumes one item of line1 line1.discounts.consume(1, offer3) assert line1.quantity_with_offer_discount(offer1) == 0 assert line1.quantity_with_offer_discount(offer2) == 1 assert line1.quantity_with_offer_discount(offer3) == 1 assert line1.quantity_with_offer_discount(offer4) == 0 assert line1.quantity_without_offer_discount(offer1) == 0 assert line1.quantity_without_offer_discount(offer2) == remaining1 assert line1.quantity_without_offer_discount(offer3) == remaining1 assert line1.quantity_without_offer_discount(offer4) == 0 # combinable offer consumes all items of line1 line1.discounts.consume(remaining1, offer2) assert line1.quantity_with_offer_discount(offer1) == 0 assert line1.quantity_with_offer_discount(offer2) == line1.quantity assert line1.quantity_with_offer_discount(offer3) == 1 assert line1.quantity_with_offer_discount(offer4) == 0 assert line1.quantity_without_offer_discount(offer1) == 0 assert line1.quantity_without_offer_discount(offer2) == 0 assert line1.quantity_without_offer_discount(offer3) == remaining1 assert line1.quantity_without_offer_discount(offer4) == 0