Parcourir la source

Fixed issue with voucher discounts not being recorded.

Also added tests for vouchers
master
David Winterbottom il y a 13 ans
Parent
révision
0d284ec0d8

+ 2
- 6
oscar/apps/offer/models.py Voir le fichier

251
             desc = _("%(value).2f discount on %(range)s") % {'value': float(self.value),
251
             desc = _("%(value).2f discount on %(range)s") % {'value': float(self.value),
252
                                                              'range': unicode(self.range).lower()}
252
                                                              'range': unicode(self.range).lower()}
253
 
253
 
254
-        max_item_str = ungettext(" (max %d item)", " (max %d items)", self.max_affected_items)
255
-        desc += max_item_str % self.max_affected_items
254
+        if self.max_affected_items:
255
+            desc += ungettext(" (max 1 item)", " (max %d items)", self.max_affected_items) % self.max_affected_items
256
 
256
 
257
         return desc
257
         return desc
258
 
258
 
752
         else:
752
         else:
753
             free_line.discount(discount, 0)
753
             free_line.discount(discount, 0)
754
         return self.round(discount)
754
         return self.round(discount)
755
-
756
-
757
-# We need to import receivers at the bottom of this script
758
-from oscar.apps.offer.receivers import receive_basket_voucher_change

+ 0
- 12
oscar/apps/offer/receivers.py Voir le fichier

16
         voucher = Voucher._default_manager.get(pk=voucher_id)
16
         voucher = Voucher._default_manager.get(pk=voucher_id)
17
         voucher.num_basket_additions += 1
17
         voucher.num_basket_additions += 1
18
         voucher.save()
18
         voucher.save()
19
-
20
-
21
-@receiver(post_save, sender=OrderDiscount)        
22
-def receive_order_discount_save(sender, instance, **kwargs):
23
-    # Record the amount of discount against the appropriate offers
24
-    # and vouchers
25
-    discount = instance
26
-    if discount.voucher:
27
-        discount.voucher.total_discount += discount.amount
28
-        discount.voucher.save()
29
-    if discount.offer:
30
-        discount.offer.record_usage(discount.amount)

+ 1
- 0
oscar/apps/offer/utils.py Voir le fichier

48
         offers = self.get_offers(request, basket) 
48
         offers = self.get_offers(request, basket) 
49
         logger.debug("Found %d offers to apply to basket %d", len(offers), basket.id)
49
         logger.debug("Found %d offers to apply to basket %d", len(offers), basket.id)
50
         discounts = self.get_basket_discounts(basket, offers)
50
         discounts = self.get_basket_discounts(basket, offers)
51
+
51
         # Store this list of discounts with the basket so it can be 
52
         # Store this list of discounts with the basket so it can be 
52
         # rendered in templates
53
         # rendered in templates
53
         basket.set_discounts(list(discounts.values()))
54
         basket.set_discounts(list(discounts.values()))

+ 15
- 6
oscar/apps/order/utils.py Voir le fichier

69
         for line in basket.all_lines():
69
         for line in basket.all_lines():
70
             self.create_line_models(order, line)
70
             self.create_line_models(order, line)
71
             self.update_stock_records(line)
71
             self.update_stock_records(line)
72
+
72
         for discount in basket.get_discounts():
73
         for discount in basket.get_discounts():
73
             self.create_discount_model(order, discount)
74
             self.create_discount_model(order, discount)
75
+            self.record_discount(discount)
76
+
74
         for voucher in basket.vouchers.all():
77
         for voucher in basket.vouchers.all():
75
             self.record_voucher_usage(order, voucher, user)
78
             self.record_voucher_usage(order, voucher, user)
76
         
79
         
78
         order_placed.send(sender=self, order=order, user=user)
81
         order_placed.send(sender=self, order=order, user=user)
79
         
82
         
80
         return order
83
         return order
84
+
85
+
81
         
86
         
82
     def create_order_model(self, user, basket, shipping_address, shipping_method, 
87
     def create_order_model(self, user, basket, shipping_address, shipping_method, 
83
                            billing_address, total_incl_tax, total_excl_tax, 
88
                            billing_address, total_incl_tax, total_excl_tax, 
200
         Creates an order discount model for each discount attached to the basket.
205
         Creates an order discount model for each discount attached to the basket.
201
         """
206
         """
202
         order_discount = OrderDiscount(order=order,
207
         order_discount = OrderDiscount(order=order,
203
-                                       offer_id=discount['offer'].id, 
208
+                                       offer_id=discount['offer'].id,
204
                                        amount=discount['discount'])
209
                                        amount=discount['discount'])
205
-        if discount['voucher']:
206
-            order_discount.voucher_id = discount['voucher'].id
207
-            order_discount.voucher_code = discount['voucher'].code
210
+        voucher = discount.get('voucher', None)
211
+        if voucher:
212
+            order_discount.voucher_id = voucher.id
213
+            order_discount.voucher_code = voucher.code
208
         order_discount.save()
214
         order_discount.save()
215
+
216
+    def record_discount(self, discount):
217
+        discount['offer'].record_usage(discount['discount'])
218
+        if 'voucher' in discount:
219
+            discount['voucher'].record_discount(discount['discount'])
209
         
220
         
210
     def record_voucher_usage(self, order, voucher, user):
221
     def record_voucher_usage(self, order, voucher, user):
211
         """
222
         """
212
         Updates the models that care about this voucher.
223
         Updates the models that care about this voucher.
213
         """
224
         """
214
         voucher.record_usage(order, user)
225
         voucher.record_usage(order, user)
215
-        voucher.num_orders += 1
216
-        voucher.save()

+ 17
- 6
oscar/apps/voucher/abstract_models.py Voir le fichier

8
 
8
 
9
 class AbstractVoucher(models.Model):
9
 class AbstractVoucher(models.Model):
10
     """
10
     """
11
-    A voucher.  This is simply a link to a collection of offers
11
+    A voucher.  This is simply a link to a collection of offers.
12
 
12
 
13
     Note that there are three possible "usage" models:
13
     Note that there are three possible "usage" models:
14
     (a) Single use
14
     (a) Single use
19
         help_text=_("""This will be shown in the checkout and basket once the voucher is entered"""))
19
         help_text=_("""This will be shown in the checkout and basket once the voucher is entered"""))
20
     code = models.CharField(_("Code"), max_length=128, db_index=True, unique=True,
20
     code = models.CharField(_("Code"), max_length=128, db_index=True, unique=True,
21
         help_text=_("""Case insensitive / No spaces allowed"""))
21
         help_text=_("""Case insensitive / No spaces allowed"""))
22
-    offers = models.ManyToManyField('offer.ConditionalOffer', related_name='vouchers', 
22
+    offers = models.ManyToManyField('offer.ConditionalOffer', related_name='vouchers',
23
                                     limit_choices_to={'offer_type': "Voucher"})
23
                                     limit_choices_to={'offer_type': "Voucher"})
24
 
24
 
25
     SINGLE_USE, MULTI_USE, ONCE_PER_CUSTOMER = ('Single use', 'Multi-use', 'Once per customer')
25
     SINGLE_USE, MULTI_USE, ONCE_PER_CUSTOMER = ('Single use', 'Multi-use', 'Once per customer')
33
     start_date = models.DateField(_('Start Date'))
33
     start_date = models.DateField(_('Start Date'))
34
     end_date = models.DateField(_('End Date'))
34
     end_date = models.DateField(_('End Date'))
35
 
35
 
36
-    # Summary information
36
+    # Audit information
37
     num_basket_additions = models.PositiveIntegerField(_('Times added to basket'), default=0)
37
     num_basket_additions = models.PositiveIntegerField(_('Times added to basket'), default=0)
38
     num_orders = models.PositiveIntegerField(_('Times on orders'), default=0)
38
     num_orders = models.PositiveIntegerField(_('Times on orders'), default=0)
39
     total_discount = models.DecimalField(_('Total discount'), decimal_places=2, max_digits=12, default=Decimal('0.00'))
39
     total_discount = models.DecimalField(_('Total discount'), decimal_places=2, max_digits=12, default=Decimal('0.00'))
59
 
59
 
60
     def is_active(self, test_date=None):
60
     def is_active(self, test_date=None):
61
         """
61
         """
62
-        Tests whether this voucher is currently active.
62
+        Test whether this voucher is currently active.
63
         """
63
         """
64
         if not test_date:
64
         if not test_date:
65
             test_date = datetime.date.today()
65
             test_date = datetime.date.today()
67
 
67
 
68
     def is_available_to_user(self, user=None):
68
     def is_available_to_user(self, user=None):
69
         """
69
         """
70
-        Tests whether this voucher is available to the passed user.
70
+        Test whether this voucher is available to the passed user.
71
         
71
         
72
         Returns a tuple of a boolean for whether it is successulf, and a message
72
         Returns a tuple of a boolean for whether it is successulf, and a message
73
         """
73
         """
96
             self.applications.create(voucher=self, order=order, user=user)
96
             self.applications.create(voucher=self, order=order, user=user)
97
         else:
97
         else:
98
             self.applications.create(voucher=self, order=order)
98
             self.applications.create(voucher=self, order=order)
99
+        self.num_orders += 1
100
+        self.save()
101
+
102
+    def record_discount(self, discount):
103
+        """
104
+        Record a discount that this offer has given
105
+        """
106
+        self.total_discount += discount
107
+        self.save()
99
 
108
 
100
     @property
109
     @property
101
     def benefit(self):
110
     def benefit(self):
120
         verbose_name_plural = _("Voucher Applications")
129
         verbose_name_plural = _("Voucher Applications")
121
 
130
 
122
     def __unicode__(self):
131
     def __unicode__(self):
123
-        return _("'%(voucher)s' used by '%(user)s'") % {'voucher': self.voucher, 'user': self.user}
132
+        return _("'%(voucher)s' used by '%(user)s'") % {
133
+            'voucher': self.voucher,
134
+            'user': self.user}

+ 0
- 2
oscar/apps/voucher/models.py Voir le fichier

7
 
7
 
8
 class VoucherApplication(AbstractVoucherApplication):
8
 class VoucherApplication(AbstractVoucherApplication):
9
     pass
9
     pass
10
-
11
-

+ 25
- 17
tests/functional/checkout_tests.py Voir le fichier

10
 from oscar.apps.basket.models import Basket
10
 from oscar.apps.basket.models import Basket
11
 from oscar.apps.order.models import Order
11
 from oscar.apps.order.models import Order
12
 from oscar.apps.address.models import Country
12
 from oscar.apps.address.models import Country
13
+from oscar.apps.voucher.models import Voucher
13
 
14
 
14
 
15
 
15
 class CheckoutMixin(object):
16
 class CheckoutMixin(object):
19
         self.client.post(reverse('basket:add'), {'product_id': product.id,
20
         self.client.post(reverse('basket:add'), {'product_id': product.id,
20
                                                  'quantity': 1})
21
                                                  'quantity': 1})
21
 
22
 
22
-    def add_voucher_to_basket(self):
23
-        voucher = create_voucher()
23
+    def add_voucher_to_basket(self, voucher=None):
24
+        if voucher is None:
25
+            voucher = create_voucher()
24
         self.client.post(reverse('basket:vouchers-add'),
26
         self.client.post(reverse('basket:vouchers-add'),
25
                          {'code': voucher.code})
27
                          {'code': voucher.code})
26
 
28
 
41
                                       'postcode': 'N1 9RT',
43
                                       'postcode': 'N1 9RT',
42
                                       'country': 'GB',
44
                                       'country': 'GB',
43
                                      })
45
                                      })
44
-        self.assertIsRedirect(response)
46
+        self.assertRedirectUrlName(response, 'checkout:shipping-method')
45
 
47
 
46
     def complete_shipping_method(self):
48
     def complete_shipping_method(self):
47
         self.client.get(reverse('checkout:shipping-method'))
49
         self.client.get(reverse('checkout:shipping-method'))
205
         self.assertIsOk(response)
207
         self.assertIsOk(response)
206
 
208
 
207
 
209
 
208
-class PaymentDetailsViewTests(ClientTestCase, CheckoutMixin):
210
+class TestPaymentDetailsView(ClientTestCase, CheckoutMixin):
209
 
211
 
210
     def test_user_must_have_a_nonempty_basket(self):
212
     def test_user_must_have_a_nonempty_basket(self):
211
         response = self.client.get(reverse('checkout:payment-details'))
213
         response = self.client.get(reverse('checkout:payment-details'))
230
         self.assertRedirectUrlName(response, 'basket:summary')
232
         self.assertRedirectUrlName(response, 'basket:summary')
231
 
233
 
232
 
234
 
233
-class OrderPlacementTests(ClientTestCase, CheckoutMixin):
235
+class TestOrderPlacement(ClientTestCase, CheckoutMixin):
234
 
236
 
235
     def setUp(self):
237
     def setUp(self):
236
         Order.objects.all().delete()
238
         Order.objects.all().delete()
237
 
239
 
238
-        super(OrderPlacementTests, self).setUp()
240
+        super(TestOrderPlacement, self).setUp()
239
         self.basket = Basket.objects.create(owner=self.user)
241
         self.basket = Basket.objects.create(owner=self.user)
240
         self.basket.add_product(create_product(price=D('12.00')))
242
         self.basket.add_product(create_product(price=D('12.00')))
241
 
243
 
253
         self.assertEqual(1, len(orders))
255
         self.assertEqual(1, len(orders))
254
         
256
         
255
 
257
 
256
-class TestAnonUserOrderPlacementScenarios(ClientTestCase, CheckoutMixin):
258
+class TestPlacingOrderUsingAVoucher(ClientTestCase, CheckoutMixin):
257
 
259
 
258
-    def test_basic_submission_gets_redirect_to_thankyou(self):
260
+    def setUp(self):
261
+        self.login()
259
         self.add_product_to_basket()
262
         self.add_product_to_basket()
263
+        voucher = create_voucher()
264
+        self.add_voucher_to_basket(voucher)
260
         self.complete_shipping_address()
265
         self.complete_shipping_address()
261
         self.complete_shipping_method()
266
         self.complete_shipping_method()
262
-        response = self.submit()
263
-        self.assertRedirectUrlName(response, 'checkout:thank-you')
267
+        self.response = self.submit()
264
 
268
 
265
-    def test_submission_using_voucher(self):
266
-        self.add_product_to_basket()
267
-        self.add_voucher_to_basket()
268
-        self.complete_shipping_address()
269
-        self.complete_shipping_method()
270
-        response = self.submit()
271
-        self.assertRedirectUrlName(response, 'checkout:thank-you')
269
+        # Reload voucher
270
+        self.voucher = Voucher.objects.get(id=voucher.id)
271
+
272
+    def test_is_successful(self):
273
+        self.assertRedirectUrlName(self.response, 'checkout:thank-you')
274
+
275
+    def test_records_use(self):
276
+        self.assertEquals(1, self.voucher.num_orders)
277
+
278
+    def test_records_discount(self):
279
+        self.assertEquals(1, self.voucher.num_orders)

+ 63
- 7
tests/unit/voucher_tests.py Voir le fichier

1
 import datetime
1
 import datetime
2
+from decimal import Decimal as D
2
 
3
 
3
 from django.test import TestCase
4
 from django.test import TestCase
5
+from django.core import exceptions
6
+from django.contrib.auth.models import User
7
+from django_dynamic_fixture import G
4
 
8
 
5
 from oscar.apps.voucher.models import Voucher
9
 from oscar.apps.voucher.models import Voucher
10
+from oscar.apps.order.models import Order
11
+
12
+START_DATE = datetime.date(2011, 01, 01)
13
+END_DATE = datetime.date(2012, 01, 01)
6
 
14
 
7
 
15
 
8
 class TestVoucher(TestCase):
16
 class TestVoucher(TestCase):
17
+
18
+    def test_saves_code_as_uppercase(self):
19
+        start = datetime.date(2011, 01, 01)
20
+        end = datetime.date(2012, 01, 01)
21
+        voucher = Voucher.objects.create(code='lower',
22
+                                         start_date=start,
23
+                                         end_date=end)
24
+        self.assertEqual('LOWER', voucher.code)
25
+
26
+    def test_checks_dates_are_sensible(self):
27
+        start = datetime.date(2011, 01, 01)
28
+        end = datetime.date(2012, 01, 01)
29
+        with self.assertRaises(exceptions.ValidationError):
30
+            voucher = Voucher.objects.create(code='lower',
31
+                                            start_date=end,
32
+                                            end_date=start)
33
+            voucher.clean()
9
     
34
     
10
     def test_is_active_between_start_and_end_dates(self):
35
     def test_is_active_between_start_and_end_dates(self):
11
         start = datetime.date(2011, 01, 01)
36
         start = datetime.date(2011, 01, 01)
20
         end = datetime.date(2011, 02, 01)
45
         end = datetime.date(2011, 02, 01)
21
         voucher = Voucher(start_date=start, end_date=end)
46
         voucher = Voucher(start_date=start, end_date=end)
22
         self.assertFalse(voucher.is_active(test))
47
         self.assertFalse(voucher.is_active(test))
23
-        
24
-    def test_codes_are_saved_as_uppercase(self):
25
-        start = datetime.date(2011, 01, 01)
26
-        end = datetime.date(2011, 02, 01)
27
-        voucher = Voucher(name="Dummy voucher", code="lowercase", start_date=start, end_date=end)
28
-        voucher.save()
29
-        self.assertEquals("LOWERCASE", voucher.code)
48
+
49
+    def test_increments_total_discount_when_recording_usage(self):
50
+        voucher = G(Voucher)
51
+        voucher.record_discount(D('10.00'))
52
+        self.assertEqual(voucher.total_discount, D('10.00'))
53
+        voucher.record_discount(D('10.00'))
54
+        self.assertEqual(voucher.total_discount, D('20.00'))
55
+
56
+
57
+class TestMultiuseVoucher(TestCase):
58
+
59
+    def setUp(self):
60
+        self.voucher = G(Voucher, usage=Voucher.MULTI_USE)
61
+
62
+    def test_is_available_to_same_user_multiple_times(self):
63
+        user, order = G(User), G(Order)
64
+        for i in xrange(10):
65
+            self.voucher.record_usage(order, user)
66
+            self.assertTrue(self.voucher.is_available_to_user(user)[0])
67
+
68
+
69
+class TestOncePerCustomerVoucher(TestCase):
70
+
71
+    def setUp(self):
72
+        self.voucher = G(Voucher, usage=Voucher.ONCE_PER_CUSTOMER)
73
+
74
+    def test_is_available_to_a_user_once(self):
75
+        user, order = G(User), G(Order)
76
+        self.assertTrue(self.voucher.is_available_to_user(user)[0])
77
+        self.voucher.record_usage(order, user)
78
+        self.assertFalse(self.voucher.is_available_to_user(user)[0])
79
+
80
+    def test_is_available_to_different_users(self):
81
+        users, order = [G(User), G(User)], G(Order)
82
+        for user in users:
83
+            self.assertTrue(self.voucher.is_available_to_user(user)[0])
84
+            self.voucher.record_usage(order, user)
85
+            self.assertFalse(self.voucher.is_available_to_user(user)[0])

Chargement…
Annuler
Enregistrer