Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

test_absolute_benefit.py 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. from decimal import Decimal as D
  2. from unittest import mock
  3. from django.core.exceptions import ValidationError
  4. from django.test import TestCase
  5. from oscar.apps.offer import models
  6. from oscar.apps.offer.utils import Applicator
  7. from oscar.test import factories
  8. from oscar.test.basket import add_product, add_products
  9. class TestAnAbsoluteDiscountAppliedWithCountConditionOnDifferentRange(TestCase):
  10. def setUp(self):
  11. self.condition_product = factories.ProductFactory()
  12. condition_range = factories.RangeFactory()
  13. condition_range.add_product(self.condition_product)
  14. self.condition = models.CountCondition.objects.create(
  15. range=condition_range,
  16. type=models.Condition.COUNT,
  17. value=2)
  18. self.benefit_product = factories.ProductFactory()
  19. benefit_range = factories.RangeFactory()
  20. benefit_range.add_product(self.benefit_product)
  21. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  22. range=benefit_range,
  23. type=models.Benefit.FIXED,
  24. value=D('3.00'))
  25. self.offer = models.ConditionalOffer(
  26. id=1, condition=self.condition, benefit=self.benefit)
  27. self.basket = factories.create_basket(empty=True)
  28. self.applicator = Applicator()
  29. def test_succcessful_application_consumes_correctly(self):
  30. add_product(self.basket, product=self.condition_product, quantity=2)
  31. add_product(self.basket, product=self.benefit_product, quantity=1)
  32. self.applicator.apply_offers(self.basket, [self.offer])
  33. discounts = self.basket.offer_applications.offer_discounts
  34. self.assertEqual(len(discounts), 1)
  35. self.assertEqual(discounts[0]['freq'], 1)
  36. def test_condition_is_consumed_correctly(self):
  37. # Testing an error case reported on the mailing list
  38. add_product(self.basket, product=self.condition_product, quantity=3)
  39. add_product(self.basket, product=self.benefit_product, quantity=2)
  40. self.applicator.apply_offers(self.basket, [self.offer])
  41. discounts = self.basket.offer_applications.offer_discounts
  42. self.assertEqual(len(discounts), 1)
  43. self.assertEqual(discounts[0]['freq'], 1)
  44. class TestAnAbsoluteDiscountAppliedWithCountCondition(TestCase):
  45. def setUp(self):
  46. range = models.Range.objects.create(
  47. name="All products", includes_all_products=True)
  48. self.condition = models.CountCondition.objects.create(
  49. range=range,
  50. type=models.Condition.COUNT,
  51. value=2)
  52. self.offer = mock.Mock()
  53. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  54. range=range,
  55. type=models.Benefit.FIXED,
  56. value=D('3.00'))
  57. self.basket = factories.create_basket(empty=True)
  58. def test_applies_correctly_to_empty_basket(self):
  59. result = self.benefit.apply(self.basket, self.condition, self.offer)
  60. self.assertEqual(D('0.00'), result.discount)
  61. self.assertEqual(0, self.basket.num_items_with_discount)
  62. self.assertEqual(0, self.basket.num_items_without_discount)
  63. def test_applies_correctly_to_basket_which_matches_condition_with_one_line(self):
  64. add_product(self.basket, price=D('12.00'), quantity=2)
  65. result = self.benefit.apply(self.basket, self.condition, self.offer)
  66. self.assertEqual(D('3.00'), result.discount)
  67. self.assertEqual(2, self.basket.num_items_with_discount)
  68. self.assertEqual(0, self.basket.num_items_without_discount)
  69. # Check the discount is applied equally to each item in the line
  70. line = self.basket.all_lines()[0]
  71. prices = line.get_price_breakdown()
  72. self.assertEqual(1, len(prices))
  73. self.assertEqual(D('10.50'), prices[0][0])
  74. def test_applies_correctly_to_basket_which_matches_condition_with_multiple_lines(self):
  75. # Use a basket with 2 lines
  76. add_products(self.basket, [
  77. (D('12.00'), 1), (D('12.00'), 1)])
  78. result = self.benefit.apply(self.basket, self.condition, self.offer)
  79. self.assertTrue(result.is_successful)
  80. self.assertFalse(result.is_final)
  81. self.assertEqual(D('3.00'), result.discount)
  82. self.assertEqual(2, self.basket.num_items_with_discount)
  83. self.assertEqual(0, self.basket.num_items_without_discount)
  84. # Check the discount is applied equally to each line
  85. for line in self.basket.all_lines():
  86. self.assertEqual(D('1.50'), line.discount_value)
  87. def test_applies_correctly_to_basket_which_matches_condition_with_multiple_lines_and_lower_total_value(self):
  88. # Use a basket with 2 lines
  89. add_products(self.basket, [
  90. (D('1.00'), 1), (D('1.50'), 1)])
  91. result = self.benefit.apply(self.basket, self.condition, self.offer)
  92. self.assertTrue(result.is_successful)
  93. self.assertFalse(result.is_final)
  94. self.assertEqual(D('2.50'), result.discount)
  95. self.assertEqual(2, self.basket.num_items_with_discount)
  96. self.assertEqual(0, self.basket.num_items_without_discount)
  97. def test_applies_correctly_to_basket_which_exceeds_condition(self):
  98. add_products(self.basket, [
  99. (D('12.00'), 2), (D('10.00'), 2)])
  100. result = self.benefit.apply(self.basket, self.condition, self.offer)
  101. self.assertEqual(D('3.00'), result.discount)
  102. self.assertEqual(4, self.basket.num_items_with_discount)
  103. self.assertEqual(0, self.basket.num_items_without_discount)
  104. def test_applies_correctly_to_basket_which_exceeds_condition_with_smaller_prices_than_discount(self):
  105. add_products(self.basket, [
  106. (D('2.00'), 2), (D('4.00'), 2)])
  107. result = self.benefit.apply(self.basket, self.condition, self.offer)
  108. self.assertEqual(D('3.00'), result.discount)
  109. self.assertEqual(4, self.basket.num_items_with_discount)
  110. self.assertEqual(0, self.basket.num_items_without_discount)
  111. def test_applies_basket_exceeding_condition_smaller_prices_than_discount_higher_prices_first(self):
  112. add_products(self.basket, [
  113. (D('2.00'), 2), (D('4.00'), 2)])
  114. result = self.benefit.apply(self.basket, self.condition, self.offer)
  115. self.assertEqual(D('3.00'), result.discount)
  116. self.assertEqual(4, self.basket.num_items_with_discount)
  117. self.assertEqual(0, self.basket.num_items_without_discount)
  118. class TestAnAbsoluteDiscount(TestCase):
  119. def setUp(self):
  120. range = models.Range.objects.create(
  121. name="All products", includes_all_products=True)
  122. self.condition = models.CountCondition.objects.create(
  123. range=range,
  124. type=models.Condition.COUNT,
  125. value=2)
  126. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  127. range=range,
  128. type=models.Benefit.FIXED,
  129. value=D('4.00'))
  130. self.offer = mock.Mock()
  131. self.basket = factories.create_basket(empty=True)
  132. def test_applies_correctly_when_discounts_need_rounding(self):
  133. # Split discount across 3 lines
  134. for price in [D('2.00'), D('2.00'), D('2.00')]:
  135. add_product(self.basket, price)
  136. result = self.benefit.apply(self.basket, self.condition, self.offer)
  137. self.assertEqual(D('4.00'), result.discount)
  138. # Check the discount is applied equally to each line
  139. line_discounts = [line.discount_value for line in self.basket.all_lines()]
  140. self.assertEqual(len(line_discounts), 3)
  141. for i, v in enumerate([D('1.33'), D('1.33'), D('1.34')]):
  142. self.assertEqual(line_discounts[i], v)
  143. class TestAnAbsoluteDiscountWithMaxItemsSetAppliedWithCountCondition(TestCase):
  144. def setUp(self):
  145. range = models.Range.objects.create(
  146. name="All products", includes_all_products=True)
  147. self.condition = models.CountCondition.objects.create(
  148. range=range,
  149. type=models.Condition.COUNT,
  150. value=2)
  151. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  152. range=range,
  153. type=models.Benefit.FIXED,
  154. value=D('3.00'),
  155. max_affected_items=1)
  156. self.offer = mock.Mock()
  157. self.basket = factories.create_basket(empty=True)
  158. def test_applies_correctly_to_empty_basket(self):
  159. result = self.benefit.apply(self.basket, self.condition, self.offer)
  160. self.assertEqual(D('0.00'), result.discount)
  161. self.assertEqual(0, self.basket.num_items_with_discount)
  162. self.assertEqual(0, self.basket.num_items_without_discount)
  163. def test_applies_correctly_to_basket_which_matches_condition(self):
  164. add_product(self.basket, D('12.00'), 2)
  165. result = self.benefit.apply(self.basket, self.condition, self.offer)
  166. self.assertEqual(D('3.00'), result.discount)
  167. self.assertEqual(1, self.basket.num_items_with_discount)
  168. self.assertEqual(1, self.basket.num_items_without_discount)
  169. def test_applies_correctly_to_basket_which_exceeds_condition(self):
  170. add_products(self.basket, [(D('12.00'), 2), (D('10.00'), 2)])
  171. result = self.benefit.apply(self.basket, self.condition, self.offer)
  172. self.assertEqual(D('3.00'), result.discount)
  173. self.assertEqual(1, self.basket.num_items_with_discount)
  174. self.assertEqual(3, self.basket.num_items_without_discount)
  175. def test_applies_correctly_to_basket_which_exceeds_condition_but_with_smaller_prices_than_discount(self):
  176. add_products(self.basket, [(D('2.00'), 2), (D('1.00'), 2)])
  177. result = self.benefit.apply(self.basket, self.condition, self.offer)
  178. self.assertEqual(D('1.00'), result.discount)
  179. self.assertEqual(1, self.basket.num_items_with_discount)
  180. self.assertEqual(3, self.basket.num_items_without_discount)
  181. class TestAnAbsoluteDiscountAppliedWithValueCondition(TestCase):
  182. def setUp(self):
  183. range = models.Range.objects.create(
  184. name="All products", includes_all_products=True)
  185. self.condition = models.ValueCondition.objects.create(
  186. range=range,
  187. type=models.Condition.VALUE,
  188. value=D('10.00'))
  189. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  190. range=range,
  191. type=models.Benefit.FIXED,
  192. value=D('3.00'))
  193. self.offer = mock.Mock()
  194. self.basket = factories.create_basket(empty=True)
  195. def test_applies_correctly_to_empty_basket(self):
  196. result = self.benefit.apply(self.basket, self.condition, self.offer)
  197. self.assertEqual(D('0.00'), result.discount)
  198. self.assertEqual(0, self.basket.num_items_with_discount)
  199. self.assertEqual(0, self.basket.num_items_without_discount)
  200. def test_applies_correctly_to_single_item_basket_which_matches_condition(self):
  201. add_products(self.basket, [(D('10.00'), 1)])
  202. result = self.benefit.apply(self.basket, self.condition, self.offer)
  203. self.assertEqual(D('3.00'), result.discount)
  204. self.assertEqual(1, self.basket.num_items_with_discount)
  205. self.assertEqual(0, self.basket.num_items_without_discount)
  206. def test_applies_correctly_to_multi_item_basket_which_matches_condition(self):
  207. add_products(self.basket, [(D('5.00'), 2)])
  208. result = self.benefit.apply(self.basket, self.condition, self.offer)
  209. self.assertEqual(D('3.00'), result.discount)
  210. self.assertEqual(2, self.basket.num_items_with_discount)
  211. self.assertEqual(0, self.basket.num_items_without_discount)
  212. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition(self):
  213. add_products(self.basket, [(D('4.00'), 3)])
  214. result = self.benefit.apply(self.basket, self.condition, self.offer)
  215. self.assertEqual(D('3.00'), result.discount)
  216. self.assertEqual(3, self.basket.num_items_with_discount)
  217. self.assertEqual(0, self.basket.num_items_without_discount)
  218. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition_but_matches_boundary(self):
  219. add_products(self.basket, [(D('5.00'), 3)])
  220. result = self.benefit.apply(self.basket, self.condition, self.offer)
  221. self.assertEqual(D('3.00'), result.discount)
  222. self.assertEqual(3, self.basket.num_items_with_discount)
  223. self.assertEqual(0, self.basket.num_items_without_discount)
  224. class TestAnAbsoluteDiscountWithMaxItemsSetAppliedWithValueCondition(TestCase):
  225. def setUp(self):
  226. range = models.Range.objects.create(
  227. name="All products", includes_all_products=True)
  228. self.condition = models.ValueCondition.objects.create(
  229. range=range,
  230. type=models.Condition.VALUE,
  231. value=D('10.00'))
  232. self.benefit = models.AbsoluteDiscountBenefit.objects.create(
  233. range=range,
  234. type=models.Benefit.FIXED,
  235. value=D('3.00'),
  236. max_affected_items=1)
  237. self.offer = mock.Mock()
  238. self.basket = factories.create_basket(empty=True)
  239. def test_applies_correctly_to_empty_basket(self):
  240. result = self.benefit.apply(self.basket, self.condition, self.offer)
  241. self.assertEqual(D('0.00'), result.discount)
  242. self.assertEqual(0, self.basket.num_items_with_discount)
  243. self.assertEqual(0, self.basket.num_items_without_discount)
  244. def test_applies_correctly_to_single_item_basket_which_matches_condition(self):
  245. add_products(self.basket, [(D('10.00'), 1)])
  246. result = self.benefit.apply(self.basket, self.condition, self.offer)
  247. self.assertEqual(D('3.00'), result.discount)
  248. self.assertEqual(1, self.basket.num_items_with_discount)
  249. self.assertEqual(0, self.basket.num_items_without_discount)
  250. def test_applies_correctly_to_multi_item_basket_which_matches_condition(self):
  251. add_products(self.basket, [(D('5.00'), 2)])
  252. result = self.benefit.apply(self.basket, self.condition, self.offer)
  253. self.assertEqual(D('3.00'), result.discount)
  254. self.assertEqual(1, self.basket.num_items_with_discount)
  255. self.assertEqual(1, self.basket.num_items_without_discount)
  256. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition(self):
  257. add_products(self.basket, [(D('4.00'), 3)])
  258. result = self.benefit.apply(self.basket, self.condition, self.offer)
  259. self.assertEqual(D('3.00'), result.discount)
  260. self.assertEqual(1, self.basket.num_items_with_discount)
  261. self.assertEqual(2, self.basket.num_items_without_discount)
  262. def test_applies_correctly_to_multi_item_basket_which_exceeds_condition_but_matches_boundary(self):
  263. add_products(self.basket, [(D('5.00'), 3)])
  264. result = self.benefit.apply(self.basket, self.condition, self.offer)
  265. self.assertEqual(D('3.00'), result.discount)
  266. self.assertEqual(1, self.basket.num_items_with_discount)
  267. self.assertEqual(2, self.basket.num_items_without_discount)
  268. def test_applies_correctly_to_multi_item_basket_which_matches_condition_but_with_lower_prices_than_discount(self):
  269. add_products(self.basket, [(D('2.00'), 6)])
  270. result = self.benefit.apply(self.basket, self.condition, self.offer)
  271. self.assertEqual(D('2.00'), result.discount)
  272. self.assertEqual(1, self.basket.num_items_with_discount)
  273. self.assertEqual(5, self.basket.num_items_without_discount)
  274. class TestAnAbsoluteDiscountBenefit(TestCase):
  275. def test_requires_a_benefit_value(self):
  276. rng = models.Range.objects.create(
  277. name="", includes_all_products=True)
  278. benefit = models.Benefit(
  279. type=models.Benefit.FIXED, range=rng
  280. )
  281. with self.assertRaises(ValidationError):
  282. benefit.clean()
  283. def test_requires_a_range(self):
  284. benefit = models.Benefit(
  285. type=models.Benefit.FIXED, value=10
  286. )
  287. with self.assertRaises(ValidationError):
  288. benefit.clean()
  289. def test_non_negative_basket_lines_values(self):
  290. # absolute benefit is larger than the line price
  291. rng = models.Range.objects.create(
  292. name="", includes_all_products=True)
  293. benefit1 = models.Benefit.objects.create(
  294. type=models.Benefit.FIXED, range=rng, value=D('100')
  295. )
  296. benefit2 = models.Benefit.objects.create(
  297. type=models.Benefit.FIXED, range=rng, value=D('100')
  298. )
  299. condition = models.ValueCondition.objects.create(
  300. range=rng,
  301. type=models.Condition.VALUE,
  302. value=D('10'))
  303. models.ConditionalOffer.objects.create(
  304. name='offer1',
  305. offer_type=models.ConditionalOffer.SITE,
  306. benefit=benefit1,
  307. condition=condition,
  308. exclusive=False
  309. )
  310. models.ConditionalOffer.objects.create(
  311. name='offer2',
  312. offer_type=models.ConditionalOffer.SITE,
  313. benefit=benefit2,
  314. condition=condition,
  315. exclusive=False
  316. )
  317. basket = factories.create_basket(empty=True)
  318. add_products(basket, [(D('20'), 1)])
  319. Applicator().apply(basket)
  320. assert len(basket.offer_applications) == 2
  321. line = basket.all_lines().first()
  322. assert line.line_price_excl_tax_incl_discounts == D(0)
  323. assert line.line_price_incl_tax_incl_discounts == D(0)
  324. assert basket.total_incl_tax == 0
  325. def test_is_discountable_works_on_child_level(self):
  326. rng = factories.RangeFactory(includes_all_products=True, name="klaazien")
  327. benefit = factories.BenefitFactory(
  328. range=rng, type=models.Benefit.PERCENTAGE, value=5, max_affected_items=100
  329. )
  330. condition = models.ValueCondition.objects.create(
  331. range=rng,
  332. type=models.Condition.COUNT,
  333. value=1
  334. )
  335. factories.ConditionalOfferFactory(
  336. priority=99999,
  337. exclusive=False,
  338. condition=condition,
  339. benefit=benefit
  340. )
  341. basket = factories.create_basket(empty=True)
  342. prod1 = factories.create_product(title="Gert is friends with Berrie", is_discountable=True, price=100)
  343. parent_discountable_product = factories.create_product(structure='parent', is_discountable=True)
  344. child = factories.create_product(
  345. title="Undiscountable variant",
  346. structure='child',
  347. parent=parent_discountable_product,
  348. is_discountable=False,
  349. price=100
  350. )
  351. parent_product = factories.create_product(structure='parent', is_discountable=False)
  352. child_discountable = factories.create_product(
  353. title="Discountable variant ", structure='child', parent=parent_product, is_discountable=True, price=200)
  354. basket.add_product(prod1, quantity=1)
  355. basket.add_product(child, quantity=2)
  356. basket.add_product(child_discountable, quantity=3)
  357. Applicator().apply(basket)
  358. line = basket.all_lines()
  359. product_actual = benefit.can_apply_benefit(line[0])
  360. assert product_actual
  361. assert prod1.is_discountable
  362. assert line[0].has_discount
  363. assert line[0].discount_value == D(5)
  364. variant_actual = benefit.can_apply_benefit(line[1])
  365. assert not variant_actual
  366. assert parent_discountable_product.is_discountable
  367. assert not child.is_discountable
  368. assert line[1].discount_value == D(0)
  369. variant_discountable_actual = benefit.can_apply_benefit(line[2])
  370. assert variant_discountable_actual
  371. assert not parent_product.is_discountable
  372. assert child_discountable.is_discountable
  373. assert line[2].has_discount
  374. assert line[2].discount_value == D(30)