Bläddra i källkod

Vouchers now work through the ordering process

master
David Winterbottom 14 år sedan
förälder
incheckning
e1c3e68f12

+ 2
- 1
oscar/basket/signals.py Visa fil

1
 import django.dispatch
1
 import django.dispatch
2
 
2
 
3
-basket_addition = django.dispatch.Signal(providing_args=["product", "user"])
3
+basket_addition = django.dispatch.Signal(providing_args=["product", "user"])
4
+basket_voucher = django.dispatch.Signal(providing_args=["basket", "voucher"])

+ 11
- 2
oscar/basket/views.py Visa fil

73
     
73
     
74
     def do_add_voucher(self, basket):
74
     def do_add_voucher(self, basket):
75
         code = self.request.POST['voucher_code']
75
         code = self.request.POST['voucher_code']
76
+        # First check if the voucher is already in the basket
77
+        try:
78
+            voucher = basket.vouchers.get(code=code)
79
+            messages.error(self.request, "You have already added the '%s' voucher to your basket" % voucher.code)
80
+            return
81
+        except ObjectDoesNotExist:    
82
+            pass
83
+        
76
         try:
84
         try:
77
             voucher = offer_models.Voucher._default_manager.get(code=code)
85
             voucher = offer_models.Voucher._default_manager.get(code=code)
78
             if not voucher.is_active():
86
             if not voucher.is_active():
79
                 messages.error(self.request, "The '%s' voucher has expired" % voucher.code)
87
                 messages.error(self.request, "The '%s' voucher has expired" % voucher.code)
80
                 return
88
                 return
81
-            if not voucher.is_available_to_user(self.request.user):
82
-                messages.error(self.request, "The '%s' voucher has already been used" % voucher.code)
89
+            is_available, message = voucher.is_available_to_user(self.request.user)
90
+            if not is_available:
91
+                messages.error(self.request, message)
83
                 return
92
                 return
84
             
93
             
85
             basket.vouchers.add(voucher)
94
             basket.vouchers.add(voucher)

+ 1
- 1
oscar/checkout/views.py Visa fil

264
             
264
             
265
             # Remove order number from session to ensure that the thank-you page is only 
265
             # Remove order number from session to ensure that the thank-you page is only 
266
             # viewable once.
266
             # viewable once.
267
-            del request.session['checkout_order_id']
267
+            #del request.session['checkout_order_id']
268
         except KeyError, ObjectDoesNotExist:
268
         except KeyError, ObjectDoesNotExist:
269
             return HttpResponseRedirect(reverse('oscar-checkout-index'))
269
             return HttpResponseRedirect(reverse('oscar-checkout-index'))
270
         return render(request, 'checkout/thank_you.html', locals())
270
         return render(request, 'checkout/thank_you.html', locals())

+ 35
- 5
oscar/offer/abstract_models.py Visa fil

44
 
44
 
45
     # Some complicated situations require offers to be applied in a set order.
45
     # Some complicated situations require offers to be applied in a set order.
46
     priority = models.IntegerField(default=0, help_text="The highest priority offers are applied first")
46
     priority = models.IntegerField(default=0, help_text="The highest priority offers are applied first")
47
+
48
+    # We track some information on usage
49
+    total_discount = models.DecimalField(decimal_places=2, max_digits=12, default=Decimal('0.00'))
50
+    
47
     date_created = models.DateTimeField(auto_now_add=True)
51
     date_created = models.DateTimeField(auto_now_add=True)
48
 
52
 
49
     objects = models.Manager()
53
     objects = models.Manager()
50
     active = ActiveOfferManager()
54
     active = ActiveOfferManager()
51
 
55
 
56
+    # We need to track the voucher that this offer came from (if it is a voucher offer)
57
+    _voucher = None
58
+
52
     class Meta:
59
     class Meta:
53
         ordering = ['-priority']
60
         ordering = ['-priority']
54
         abstract = True
61
         abstract = True
77
             self._proxy_condition().consume_items(basket)
84
             self._proxy_condition().consume_items(basket)
78
         return discount    
85
         return discount    
79
         
86
         
87
+    def set_voucher(self, voucher):
88
+        self._voucher = voucher
89
+        
90
+    def get_voucher(self):
91
+        return self._voucher        
92
+        
80
     def _proxy_condition(self):
93
     def _proxy_condition(self):
81
         u"""
94
         u"""
82
         Returns the appropriate proxy model for the condition
95
         Returns the appropriate proxy model for the condition
247
     def is_available_to_user(self, user=None):
260
     def is_available_to_user(self, user=None):
248
         u"""
261
         u"""
249
         Tests whether this voucher is available to the passed user.
262
         Tests whether this voucher is available to the passed user.
263
+        
264
+        Returns a tuple of a boolean for whether it is successulf, and a message
250
         """
265
         """
266
+        is_available, message = False, ''
251
         if self.usage == self.SINGLE_USE:
267
         if self.usage == self.SINGLE_USE:
252
-            return self.applications.count() == 0
268
+            is_available = self.applications.count() == 0
269
+            if not is_available:
270
+                message = "This voucher has already been used"
253
         elif self.usage == self.MULTI_USE:
271
         elif self.usage == self.MULTI_USE:
254
-            return True
272
+            is_available = True
255
         elif self.usage == self.ONCE_PER_CUSTOMER:
273
         elif self.usage == self.ONCE_PER_CUSTOMER:
256
             if not user.is_authenticated():
274
             if not user.is_authenticated():
257
-                return False
275
+                is_available = False
276
+                message = "This voucher is only available to signed in users"
258
             else:
277
             else:
259
-                return self.applications.filter(voucher=self, user=user).count() == 0
260
-        return False
278
+                is_available = self.applications.filter(voucher=self, user=user).count() == 0
279
+                if not is_available:
280
+                    message = "You have already used this voucher in a previous order"
281
+        return is_available, message
282
+    
283
+    def record_usage(self, user):
284
+        u"""
285
+        Records a usage of this voucher in an order.
286
+        """
287
+        self.applications.create(voucher=self, user=user)
261
 
288
 
262
 
289
 
263
 class AbstractVoucherApplication(models.Model):
290
 class AbstractVoucherApplication(models.Model):
272
 
299
 
273
     class Meta:
300
     class Meta:
274
         abstract = True
301
         abstract = True
302
+        
303
+    def __unicode__(self):
304
+        return u"'%s' used by '%s'" % (self.voucher, self.user)
275
 
305
 
276
 
306
 
277
 
307
 

+ 27
- 2
oscar/offer/admin.py Visa fil

13
 class VoucherAdmin(admin.ModelAdmin):
13
 class VoucherAdmin(admin.ModelAdmin):
14
     list_display = ('name', 'code', 'usage', 'num_basket_additions', 'num_orders', 'total_discount')    
14
     list_display = ('name', 'code', 'usage', 'num_basket_additions', 'num_orders', 'total_discount')    
15
     readonly_fields = ('num_basket_additions', 'num_orders', 'total_discount')
15
     readonly_fields = ('num_basket_additions', 'num_orders', 'total_discount')
16
+    fieldsets = (
17
+        (None, {
18
+            'fields': ('name', 'code', 'usage', 'start_date', 'end_date')
19
+        }),
20
+        ('Benefit', {
21
+            'fields': ('offers', 'free_shipping')
22
+        }),
23
+        ('Usage', {
24
+            'fields': ('num_basket_additions', 'num_orders', 'total_discount')
25
+        }),
26
+        
27
+    )
28
+    
29
+class VoucherApplicationAdmin(admin.ModelAdmin):
30
+    list_display = ('voucher', 'user', 'date_created')
31
+    readonly_fields = ('voucher', 'user')        
16
     
32
     
17
 class ConditionalOfferAdmin(admin.ModelAdmin):
33
 class ConditionalOfferAdmin(admin.ModelAdmin):
18
-    list_display = ('name', 'offer_type', 'start_date', 'end_date', 'condition', 'benefit')
34
+    list_display = ('name', 'offer_type', 'start_date', 'end_date', 'condition', 'benefit', 'total_discount')
19
     list_filter = ('offer_type',)
35
     list_filter = ('offer_type',)
36
+    readonly_fields = ('total_discount',)
37
+    fieldsets = (
38
+        (None, {
39
+            'fields': ('name', 'description', 'offer_type', 'condition', 'benefit', 'start_date', 'end_date', 'priority')
40
+        }),
41
+        ('Usage', {
42
+            'fields': ('total_discount',)
43
+        }),
44
+    )
20
 
45
 
21
 admin.site.register(models.ConditionalOffer, ConditionalOfferAdmin)
46
 admin.site.register(models.ConditionalOffer, ConditionalOfferAdmin)
22
 admin.site.register(models.Condition, ConditionAdmin)
47
 admin.site.register(models.Condition, ConditionAdmin)
23
 admin.site.register(models.Benefit, BenefitAdmin)
48
 admin.site.register(models.Benefit, BenefitAdmin)
24
 admin.site.register(models.Range)
49
 admin.site.register(models.Range)
25
 admin.site.register(models.Voucher, VoucherAdmin)
50
 admin.site.register(models.Voucher, VoucherAdmin)
26
-admin.site.register(models.VoucherApplication)
51
+admin.site.register(models.VoucherApplication, VoucherApplicationAdmin)

+ 10
- 10
oscar/offer/fixtures/sample-voucher.json Visa fil

1
 [
1
 [
2
+    {
3
+        "pk": 1, 
4
+        "model": "offer.range", 
5
+        "fields": {
6
+            "includes_all_products": true, 
7
+            "excluded_products": [], 
8
+            "name": "Whole site", 
9
+            "included_products": []
10
+        }
11
+    }, 
2
     {
12
     {
3
         "pk": 1, 
13
         "pk": 1, 
4
         "model": "offer.condition", 
14
         "model": "offer.condition", 
33
             "description": ""
43
             "description": ""
34
         }
44
         }
35
     }, 
45
     }, 
36
-    {
37
-        "pk": 1, 
38
-        "model": "offer.range", 
39
-        "fields": {
40
-            "includes_all_products": true, 
41
-            "excluded_products": [], 
42
-            "name": "Whole site", 
43
-            "included_products": []
44
-        }
45
-    }, 
46
     {
46
     {
47
         "pk": 1, 
47
         "pk": 1, 
48
         "model": "offer.voucher", 
48
         "model": "offer.voucher", 

+ 4
- 1
oscar/offer/models.py Visa fil

209
     pass
209
     pass
210
 
210
 
211
 class VoucherApplication(AbstractVoucherApplication):
211
 class VoucherApplication(AbstractVoucherApplication):
212
-    pass
212
+    pass
213
+
214
+# We need to import receivers at the bottom of this script
215
+from oscar.offer.receivers import receive_basket_voucher_change

+ 29
- 0
oscar/offer/receivers.py Visa fil

1
+from django.dispatch import receiver
2
+from django.db.models.signals import m2m_changed, post_save
3
+
4
+from oscar.services import import_module
5
+offer_models = import_module('offer.models', ['Voucher'])
6
+order_models = import_module('order.models', ['OrderDiscount'])
7
+
8
+@receiver(m2m_changed)
9
+def receive_basket_voucher_change(sender, **kwargs):
10
+    if kwargs['model'] == offer_models.Voucher and kwargs['action'] == 'post_add':
11
+        voucher_id = list(kwargs['pk_set'])[0]
12
+        voucher = offer_models.Voucher._default_manager.get(pk=voucher_id)
13
+        voucher.num_basket_additions += 1
14
+        voucher.save()
15
+
16
+@receiver(post_save, sender=order_models.OrderDiscount)        
17
+def receive_order_discount_save(sender, instance, **kwargs):
18
+    # Record the amount of discount against the appropriate offers
19
+    # and vouchers
20
+    discount = instance
21
+    if discount.voucher:
22
+        discount.voucher.total_discount += discount.amount
23
+        discount.voucher.save()
24
+    discount.offer.total_discount += discount.amount
25
+    discount.offer.save()
26
+    
27
+    
28
+        
29
+    

+ 8
- 3
oscar/offer/utils.py Visa fil

13
     def apply(self, request, basket):
13
     def apply(self, request, basket):
14
         u"""
14
         u"""
15
         Applies all relevant offers to the given basket.  The user is passed 
15
         Applies all relevant offers to the given basket.  The user is passed 
16
-        too as sometimes the available offers are dependent on the user
16
+        too as sometimes the available offers are dependent on the user.
17
         """
17
         """
18
         offers = self.get_offers(request, basket)
18
         offers = self.get_offers(request, basket)
19
         discounts = {}
19
         discounts = {}
25
                 if discount > 0:
25
                 if discount > 0:
26
                     if offer.id not in discounts:
26
                     if offer.id not in discounts:
27
                         discounts[offer.id] = {'name': offer.name,
27
                         discounts[offer.id] = {'name': offer.name,
28
+                                               'offer': offer,
29
+                                               'voucher': offer.get_voucher(),
28
                                                'freq': 0,
30
                                                'freq': 0,
29
                                                'discount': Decimal('0.00')} 
31
                                                'discount': Decimal('0.00')} 
30
                     discounts[offer.id]['discount'] += discount
32
                     discounts[offer.id]['discount'] += discount
31
                     discounts[offer.id]['freq'] += 1
33
                     discounts[offer.id]['freq'] += 1
32
                 else:
34
                 else:
33
                     break
35
                     break
34
-                
36
+               
35
         # Store this list of discounts with the basket so it can be 
37
         # Store this list of discounts with the basket so it can be 
36
         # rendered in templates
38
         # rendered in templates
37
         basket.set_discounts(list(discounts.values()))
39
         basket.set_discounts(list(discounts.values()))
63
         offers = []
65
         offers = []
64
         for voucher in basket.vouchers.all():
66
         for voucher in basket.vouchers.all():
65
             if voucher.is_active() and voucher.is_available_to_user(user):
67
             if voucher.is_active() and voucher.is_available_to_user(user):
66
-                offers = list(chain(offers, voucher.offers.all()))
68
+                basket_offers = voucher.offers.all()
69
+                for offer in basket_offers:
70
+                    offer.set_voucher(voucher)
71
+                offers = list(chain(offers, basket_offers))
67
         return offers
72
         return offers
68
     
73
     
69
     def get_user_offers(self, user):
74
     def get_user_offers(self, user):

+ 14
- 1
oscar/order/abstract_models.py Visa fil

174
     line_price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
174
     line_price_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
175
     line_price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
175
     line_price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
176
     
176
     
177
+    # Price information before discounts are applied
178
+    line_price_before_discounts_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
179
+    line_price_before_discounts_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
180
+    
177
     # Cost price (the price charged by the fulfilment partner for this product).  This
181
     # Cost price (the price charged by the fulfilment partner for this product).  This
178
     # is useful for audit and financial reporting.
182
     # is useful for audit and financial reporting.
179
     cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
183
     cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
418
         return self.name
422
         return self.name
419
         
423
         
420
         
424
         
421
-
425
+class AbstractOrderDiscount(models.Model):
426
+    
427
+    order = models.ForeignKey('order.Order', related_name="discounts")
428
+    offer = models.ForeignKey('offer.ConditionalOffer', null=True, on_delete=models.SET_NULL)
429
+    voucher = models.ForeignKey('offer.Voucher', related_name="discount_vouchers", null=True, on_delete=models.SET_NULL)
430
+    voucher_code = models.CharField(_("Code"), max_length=128, db_index=True)
431
+    amount = models.DecimalField(decimal_places=2, max_digits=12, default=0)
432
+    
433
+    class Meta:
434
+        abstract = True

+ 6
- 1
oscar/order/admin.py Visa fil

4
 models = import_module('order.models', ['Order', 'OrderNote', 'CommunicationEvent', 'CommunicationEventType',
4
 models = import_module('order.models', ['Order', 'OrderNote', 'CommunicationEvent', 'CommunicationEventType',
5
                                         'BillingAddress', 'ShippingAddress', 'Line',
5
                                         'BillingAddress', 'ShippingAddress', 'Line',
6
                                         'LinePrice', 'ShippingEvent', 'ShippingEventType', 
6
                                         'LinePrice', 'ShippingEvent', 'ShippingEventType', 
7
-                                        'PaymentEvent', 'PaymentEventType', 'LineAttribute'])
7
+                                        'PaymentEvent', 'PaymentEventType', 'LineAttribute', 'OrderDiscount'])
8
 
8
 
9
 class OrderAdmin(admin.ModelAdmin):
9
 class OrderAdmin(admin.ModelAdmin):
10
     list_display = ('number', 'total_incl_tax', 'site', 'user', 'billing_address', 'date_placed')
10
     list_display = ('number', 'total_incl_tax', 'site', 'user', 'billing_address', 'date_placed')
33
         if not change:
33
         if not change:
34
             obj.user = request.user
34
             obj.user = request.user
35
         obj.save()
35
         obj.save()
36
+        
37
+class OrderDiscountAdmin(admin.ModelAdmin):
38
+    readonly_fields = ('order' ,'offer', 'voucher', 'voucher_code', 'amount')
39
+    list_display = ('order' ,'offer', 'voucher', 'voucher_code', 'amount')
36
 
40
 
37
 admin.site.register(models.Order, OrderAdmin)
41
 admin.site.register(models.Order, OrderAdmin)
38
 admin.site.register(models.ShippingAddress)
42
 admin.site.register(models.ShippingAddress)
43
 admin.site.register(models.PaymentEvent)
47
 admin.site.register(models.PaymentEvent)
44
 admin.site.register(models.PaymentEventType, PaymentEventTypeAdmin)
48
 admin.site.register(models.PaymentEventType, PaymentEventTypeAdmin)
45
 admin.site.register(models.LineAttribute)
49
 admin.site.register(models.LineAttribute)
50
+admin.site.register(models.OrderDiscount, OrderDiscountAdmin)
46
 
51
 

+ 4
- 0
oscar/order/fixtures/sample-order.json Visa fil

44
             "line_price_excl_tax": "69.90", 
44
             "line_price_excl_tax": "69.90", 
45
             "partner_reference": null, 
45
             "partner_reference": null, 
46
             "line_price_incl_tax": "69.90", 
46
             "line_price_incl_tax": "69.90", 
47
+            "line_price_before_discounts_incl_tax": "69.90", 
48
+            "line_price_before_discounts_excl_tax": "69.90", 
47
             "order": 1, 
49
             "order": 1, 
48
             "quantity": 10
50
             "quantity": 10
49
         }
51
         }
58
             "line_price_excl_tax": "68.80", 
60
             "line_price_excl_tax": "68.80", 
59
             "partner_reference": null, 
61
             "partner_reference": null, 
60
             "line_price_incl_tax": "68.80", 
62
             "line_price_incl_tax": "68.80", 
63
+            "line_price_before_discounts_incl_tax": "68.80", 
64
+            "line_price_before_discounts_excl_tax": "68.80", 
61
             "order": 1, 
65
             "order": 1, 
62
             "quantity": 5
66
             "quantity": 5
63
         }
67
         }

+ 3
- 0
oscar/order/models.py Visa fil

43
 class PaymentEventType(AbstractPaymentEventType):
43
 class PaymentEventType(AbstractPaymentEventType):
44
     pass
44
     pass
45
 
45
 
46
+class OrderDiscount(AbstractOrderDiscount):
47
+    pass
48
+
46
 
49
 
47
     
50
     

+ 23
- 2
oscar/order/utils.py Visa fil

2
 
2
 
3
 from oscar.services import import_module
3
 from oscar.services import import_module
4
 order_models = import_module('order.models', ['ShippingAddress', 'Order', 'Line', 
4
 order_models = import_module('order.models', ['ShippingAddress', 'Order', 'Line', 
5
-                                              'LinePrice', 'LineAttribute'])
5
+                                              'LinePrice', 'LineAttribute', 'OrderDiscount'])
6
 order_signals = import_module('order.signals', ['order_placed'])
6
 order_signals = import_module('order.signals', ['order_placed'])
7
 
7
 
8
 class OrderNumberGenerator(object):
8
 class OrderNumberGenerator(object):
39
         order = self._create_order_model(user, basket, shipping_address, shipping_method, order_number)
39
         order = self._create_order_model(user, basket, shipping_address, shipping_method, order_number)
40
         for line in basket.all_lines():
40
         for line in basket.all_lines():
41
             self._create_line_models(order, line)
41
             self._create_line_models(order, line)
42
+        for discount in basket.discounts:
43
+            self._create_discount_model(order, discount)
44
+        for voucher in basket.vouchers.all():
45
+            self._record_voucher_usage(order, voucher, user)
42
         
46
         
43
         basket.set_as_submitted()
47
         basket.set_as_submitted()
44
         
48
         
79
                                       title=basket_line.product.get_title(),
83
                                       title=basket_line.product.get_title(),
80
                                       quantity=basket_line.quantity, 
84
                                       quantity=basket_line.quantity, 
81
                                       line_price_excl_tax=basket_line.line_price_excl_tax_and_discounts, 
85
                                       line_price_excl_tax=basket_line.line_price_excl_tax_and_discounts, 
82
-                                      line_price_incl_tax=basket_line.line_price_incl_tax_and_discounts)
86
+                                      line_price_incl_tax=basket_line.line_price_incl_tax_and_discounts,
87
+                                      line_price_before_discounts_excl_tax=basket_line.line_price_excl_tax,
88
+                                      line_price_before_discounts_incl_tax=basket_line.line_price_incl_tax,)
83
         if basket_line.product.has_stockrecord:
89
         if basket_line.product.has_stockrecord:
84
             order_line.partner_reference = basket_line.product.stockrecord.partner_reference
90
             order_line.partner_reference = basket_line.product.stockrecord.partner_reference
85
             order_line.dispatch_date = basket_line.product.stockrecord.dispatch_date
91
             order_line.dispatch_date = basket_line.product.stockrecord.dispatch_date
102
         for attr in basket_line.attributes.all():
108
         for attr in basket_line.attributes.all():
103
             order_models.LineAttribute._default_manager.create(line=order_line, type=attr.option.code,
109
             order_models.LineAttribute._default_manager.create(line=order_line, type=attr.option.code,
104
                                                       value=attr.value)
110
                                                       value=attr.value)
111
+            
112
+    def _create_discount_model(self, order, discount):
113
+        u"""
114
+        Creates an order discount model for each discount attached to the basket.
115
+        """
116
+        order_discount = order_models.OrderDiscount(order=order, offer=discount['offer'], amount=discount['discount'])
117
+        if discount['voucher']:
118
+            order_discount.voucher = discount['voucher']
119
+            order_discount.voucher_code = discount['voucher'].code
120
+        order_discount.save()
121
+        
122
+    def _record_voucher_usage(self, order, voucher, user):
123
+        voucher.record_usage(user)
124
+        voucher.num_orders += 1
125
+        voucher.save()

+ 1
- 1
oscar/templates/basket/summary.html Visa fil

89
     <li>
89
     <li>
90
         <form action="{% url oscar-basket %}" method="POST">
90
         <form action="{% url oscar-basket %}" method="POST">
91
             {% csrf_token %}
91
             {% csrf_token %}
92
-            {{ voucher.code }}
92
+            {{ voucher.name }} ({{ voucher.code }})
93
             <input type="hidden" name="action" value="remove_voucher" />
93
             <input type="hidden" name="action" value="remove_voucher" />
94
             <input type="hidden" name="voucher_code" value="{{ voucher.code }}" />
94
             <input type="hidden" name="voucher_code" value="{{ voucher.code }}" />
95
             <input type="submit" value="Remove" />
95
             <input type="submit" value="Remove" />

+ 11
- 2
oscar/templates/checkout/thank_you.html Visa fil

25
         <td><a href="{{ line.product.get_absolute_url }}">{{ line.description }}</a></td>
25
         <td><a href="{{ line.product.get_absolute_url }}">{{ line.description }}</a></td>
26
         <td>{{ line.est_dispatch_date }}</td>
26
         <td>{{ line.est_dispatch_date }}</td>
27
         <td>{{ line.quantity }}</td>
27
         <td>{{ line.quantity }}</td>
28
-        <td>{{ line.line_price_excl_tax|currency }}</td>
29
-        <td>{{ line.line_price_incl_tax|currency }}</td>
28
+        <td>{{ line.line_price_before_discounts_excl_tax|currency }}</td>
29
+        <td>{{ line.line_price_before_discounts_incl_tax|currency }}</td>
30
+    </tr>
31
+    {% endfor %}
32
+    {% for discount in order.discounts.all %}
33
+    <tr>
34
+        <td>{{ discount.offer }}</td>
35
+        <td></td>
36
+        <td></td>
37
+        <td></td>
38
+        <td>- {{ discount.amount|currency }}</td>
30
     </tr>
39
     </tr>
31
     {% endfor %}
40
     {% endfor %}
32
     <tr>
41
     <tr>

Laddar…
Avbryt
Spara