Browse Source

Added new fields to several models to handle reporting requirements

master
David Winterbottom 14 years ago
parent
commit
54821c91ee

+ 13
- 7
oscar/apps/order/abstract_models.py View File

@@ -164,9 +164,11 @@ class AbstractLine(models.Model):
164 164
     order = models.ForeignKey('order.Order', related_name='lines')
165 165
     
166 166
     # We store the partner, their SKU and the title for cases where the product has been
167
-    # deleted from the catalogue.
168
-    partner = models.ForeignKey('stock.Partner', related_name='order_lines')
169
-    partner_sku = models.CharField(_("Partner SKU"), max_length=128, blank=True, null=True)
167
+    # deleted from the catalogue.  We also store the partner name in case the partner
168
+    # gets deleted at a later date.
169
+    partner = models.ForeignKey('stock.Partner', related_name='order_lines', blank=True, null=True, on_delete=models.SET_NULL)
170
+    partner_name = models.CharField(_("Partner name"), max_length=128)
171
+    partner_sku = models.CharField(_("Partner SKU"), max_length=128)
170 172
     title = models.CharField(_("Title"), max_length=255)
171 173
     
172 174
     # We don't want any hard links between orders and the products table
@@ -182,9 +184,14 @@ class AbstractLine(models.Model):
182 184
     line_price_before_discounts_incl_tax = models.DecimalField(decimal_places=2, max_digits=12)
183 185
     line_price_before_discounts_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
184 186
     
185
-    # Cost price (the price charged by the fulfilment partner for this product).  This
186
-    # is useful for audit and financial reporting.
187
-    cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
187
+
188
+    # REPORTING FIELDS        
189
+    # Cost price (the price charged by the fulfilment partner for this product).
190
+    unit_cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
191
+    # Normal site price for item (without discounts)
192
+    unit_site_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
193
+    # Retail price at time of purchase
194
+    unit_retail_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
188 195
     
189 196
     # Partner information
190 197
     partner_line_reference = models.CharField(_("Partner reference"), max_length=128, blank=True, null=True,
@@ -194,7 +201,6 @@ class AbstractLine(models.Model):
194 201
     # Estimated dispatch date - should be set at order time
195 202
     est_dispatch_date = models.DateField(blank=True, null=True)
196 203
     
197
-    
198 204
     @property
199 205
     def description(self):
200 206
         u"""

+ 33
- 21
oscar/apps/order/utils.py View File

@@ -2,9 +2,10 @@ from django.contrib.sites.models import Site
2 2
 
3 3
 from oscar.core.loading import import_module
4 4
 import_module('order.models', ['ShippingAddress', 'Order', 'Line', 
5
-                                              'LinePrice', 'LineAttribute', 'OrderDiscount'], locals())
5
+                               'LinePrice', 'LineAttribute', 'OrderDiscount'], locals())
6 6
 import_module('order.signals', ['order_placed'], locals())
7 7
 
8
+
8 9
 class OrderNumberGenerator(object):
9 10
     u"""
10 11
     Simple object for generating order numbers.
@@ -47,7 +48,7 @@ class OrderCreator(object):
47 48
         basket.set_as_submitted()
48 49
         
49 50
         # Send signal for analytics to pick up
50
-        order_signals.order_placed.send(sender=self, order=order, user=user)
51
+        order_placed.send(sender=self, order=order, user=user)
51 52
         
52 53
         return order
53 54
         
@@ -77,18 +78,29 @@ class OrderCreator(object):
77 78
     
78 79
     def _create_line_models(self, order, basket_line):
79 80
         u"""Creates the batch line model."""
80
-        order_line = order_models.Line(order=order,
81
-                                      partner=self._get_partner_for_product(basket_line.product),
82
-                                      product=basket_line.product, 
83
-                                      title=basket_line.product.get_title(),
84
-                                      quantity=basket_line.quantity, 
85
-                                      line_price_excl_tax=basket_line.line_price_excl_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,)
89
-        if basket_line.product.has_stockrecord:
90
-            order_line.partner_sku = basket_line.product.stockrecord.partner_sku
91
-            order_line.est_dispatch_date = basket_line.product.stockrecord.dispatch_date
81
+        partner = self._get_partner_for_product(basket_line.product)
82
+        stockrecord = basket_line.product.stockrecord
83
+        order_line = Line(order=order,
84
+                          # Partner details
85
+                          partner=partner,
86
+                          partner_name=partner.name,
87
+                          partner_sku=stockrecord.partner_sku,
88
+                          # Product details
89
+                          product=basket_line.product, 
90
+                          title=basket_line.product.get_title(),
91
+                          quantity=basket_line.quantity,
92
+                          # Price details 
93
+                          line_price_excl_tax=basket_line.line_price_excl_tax_and_discounts, 
94
+                          line_price_incl_tax=basket_line.line_price_incl_tax_and_discounts,
95
+                          line_price_before_discounts_excl_tax=basket_line.line_price_excl_tax,
96
+                          line_price_before_discounts_incl_tax=basket_line.line_price_incl_tax,
97
+                          # Reporting details
98
+                          unit_cost_price = stockrecord.cost_price,
99
+                          unit_site_price = stockrecord.price_incl_tax,
100
+                          unit_retail_price = stockrecord.price_retail,
101
+                          # Shipping details
102
+                          est_dispatch_date = basket_line.product.stockrecord.dispatch_date
103
+                          )
92 104
         order_line.save()
93 105
         self._create_line_price_models(order, order_line, basket_line)
94 106
         self._create_line_attributes(order, order_line, basket_line)
@@ -98,18 +110,18 @@ class OrderCreator(object):
98 110
         breakdown = basket_line.get_price_breakdown()
99 111
         for price_incl_tax, price_excl_tax, quantity in breakdown:
100 112
             LinePrice._default_manager.create(order=order,
101
-                                                  line=order_line, 
102
-                                                  quantity=quantity, 
103
-                                                  price_incl_tax=price_incl_tax,
104
-                                                  price_excl_tax=price_excl_tax)
113
+                                              line=order_line, 
114
+                                              quantity=quantity, 
115
+                                              price_incl_tax=price_incl_tax,
116
+                                              price_excl_tax=price_excl_tax)
105 117
     
106 118
     def _create_line_attributes(self, order, order_line, basket_line):
107 119
         u"""Creates the batch line attributes."""
108 120
         for attr in basket_line.attributes.all():
109 121
             LineAttribute._default_manager.create(line=order_line,
110
-                                                               option=attr.option, 
111
-                                                               type=attr.option.code,
112
-                                                               value=attr.value)
122
+                                                   option=attr.option, 
123
+                                                   type=attr.option.code,
124
+                                                   value=attr.value)
113 125
             
114 126
     def _create_discount_model(self, order, discount):
115 127
         u"""

+ 1
- 1
oscar/apps/product/abstract_models.py View File

@@ -88,7 +88,7 @@ class AbstractItem(models.Model):
88 88
     date_created = models.DateTimeField(auto_now_add=True)
89 89
 
90 90
     # This field is used by Haystack to reindex search
91
-    date_updated = models.DateTimeField(auto_now=True, null=True, default=None)
91
+    date_updated = models.DateTimeField(auto_now=True, db_index=True)
92 92
 
93 93
     objects = models.Manager()
94 94
     browsable = BrowsableItemManager()

+ 1
- 1
oscar/apps/search/forms.py View File

@@ -25,7 +25,7 @@ class MultiFacetedSearchForm(FacetedSearchForm):
25 25
         Overriding the search method to allow for multiple facets
26 26
         '''
27 27
         sqs = super(FacetedSearchForm, self).search().order_by('-score')
28
-        if hasattr(self, 'cleaned_data') and self.cleaned_data['selected_facets']:
28
+        if hasattr(self, 'cleaned_data') and 'selected_facets' in self.cleaned_data:
29 29
             for f in self.cleaned_data['selected_facets'].split("|"):
30 30
                 sqs = sqs.narrow(f)
31 31
         return sqs

+ 2
- 2
oscar/apps/search/views.py View File

@@ -1,8 +1,8 @@
1 1
 import json
2
-import settings
3 2
 
4 3
 from django.http import HttpResponse, HttpResponseRedirect
5 4
 from django.views.generic.base import View
5
+from django.conf import settings
6 6
 from haystack.query import SearchQuerySet
7 7
 from haystack.views import FacetedSearchView
8 8
 
@@ -85,7 +85,7 @@ class MultiFacetedSearchView(FacetedSearchView):
85 85
         '''
86 86
         extra = super(MultiFacetedSearchView, self).extra_context()
87 87
 
88
-        if hasattr(self.form, 'cleaned_data') and self.form.cleaned_data['selected_facets']:
88
+        if hasattr(self.form, 'cleaned_data') and 'selected_facets' in self.form.cleaned_data:
89 89
             extra['facets_applied'] = []
90 90
             for f in self.form.cleaned_data['selected_facets'].split("|"):
91 91
                 facet = f.split(":")

+ 26
- 10
oscar/apps/stock/abstract_models.py View File

@@ -11,7 +11,7 @@ class AbstractPartner(models.Model):
11 11
     
12 12
     # A partner can have users assigned to it.  These can be used
13 13
     # to provide authentication for webservices etc.
14
-    users = models.ManyToManyField('auth.User', releatd_name="partners", null=True)
14
+    users = models.ManyToManyField('auth.User', related_name="partners", null=True)
15 15
     
16 16
     class Meta:
17 17
         verbose_name_plural = 'Fulfillment partners'
@@ -48,28 +48,38 @@ class AbstractStockRecord(models.Model):
48 48
     
49 49
     # This is the base price for calculations - tax should be applied 
50 50
     # by the appropriate method.  We don't store it here as its calculation is 
51
-    # highly domain-specific.
52
-    price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12)
51
+    # highly domain-specific.  It is NULLable because some items don't have a fixed
52
+    # price.
53
+    price_excl_tax = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
54
+    
55
+    # Retail price for this item
56
+    price_retail = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
53 57
     
54 58
     # Cost price is optional as not all partner supply it
55 59
     cost_price = models.DecimalField(decimal_places=2, max_digits=12, blank=True, null=True)
56 60
     
57 61
     # Stock level information
58
-    num_in_stock = models.IntegerField(default=0)
59
-    num_allocated = models.IntegerField(default=0)
62
+    num_in_stock = models.IntegerField(default=0, blank=True, null=True)
63
+    num_allocated = models.IntegerField(default=0, blank=True, null=True)
64
+    
65
+    # Date information
66
+    date_created = models.DateTimeField(auto_now_add=True)
67
+    date_updated = models.DateTimeField(auto_now=True, db_index=True)
60 68
     
61 69
     class Meta:
62 70
         abstract = True
63 71
     
64 72
     def decrement_num_in_stock(self, delta):
65
-        u"""Decrement an item's stock level"""
73
+        """
74
+        Decrement an item's stock level
75
+        """
66 76
         if self.num_in_stock >= delta:
67 77
             self.num_in_stock -= delta
68 78
         self.num_allocated += delta
69 79
         self.save()
70 80
         
71 81
     def set_discount_price(self, price):
72
-        u"""
82
+        """
73 83
         A setter method for setting a new price.  
74 84
         
75 85
         This is called from within the "discount" app, which is responsible
@@ -96,14 +106,20 @@ class AbstractStockRecord(models.Model):
96 106
     
97 107
     @property 
98 108
     def price_incl_tax(self):
99
-        u"""Return a product's price including tax"""
100
-        return self.price_excl_tax
109
+        """
110
+        Return a product's price including tax.
111
+        
112
+        This defaults to the price_excl_tax as tax calculations are 
113
+        domain specific.  This class needs to be subclassed and tax logic
114
+        added to this method.
115
+        """
116
+        return self.price_excl_tax + self.price_tax
101 117
     
102 118
     @property 
103 119
     def price_tax(self):
104 120
         u"""Return a product's tax value"""
105 121
         return 0
106
-        
122
+    
107 123
     def __unicode__(self):
108 124
         if self.partner_sku:
109 125
             return "%s (%s): %s" % (self.partner.name, self.partner_sku, self.product.title)

Loading…
Cancel
Save