Procházet zdrojové kódy

Do not perform stock allocation manipulations on non-stock-tracked products (#2567)

* Silently skip stock allocation manipulations if the product class doesn't track stock.

* Make it possible to cancel/consume stock allocations without line args.

You can now do:
consume_stock_allocations(order)
cancel_stock_allocations(order)
without the lines / line_quantities arguments.
master
Kees Hink před 7 roky
rodič
revize
6e6ef5cf72

+ 26
- 4
src/oscar/apps/order/processing.py Zobrazit soubor

@@ -180,27 +180,49 @@ class EventHandler(object):
180 180
         """
181 181
         Check whether stock records still have enough stock to honour the
182 182
         requested allocations.
183
+
184
+        Lines whose product doesn't track stock are disregarded, which means
185
+        this method will return True if only non-stock-tracking-lines are
186
+        passed.
187
+        This means you can just throw all order lines to this method, without
188
+        checking whether stock tracking is enabled or not.
189
+        This is okay, as calling consume_stock_allocations() has no effect for
190
+        non-stock-tracking lines.
183 191
         """
184 192
         for line, qty in zip(lines, line_quantities):
185 193
             record = line.stockrecord
186 194
             if not record:
187 195
                 return False
196
+            if not record.can_track_allocations:
197
+                continue
188 198
             if not record.is_allocation_consumption_possible(qty):
189 199
                 return False
190 200
         return True
191 201
 
192
-    def consume_stock_allocations(self, order, lines, line_quantities):
202
+    def consume_stock_allocations(self, order, lines=None, line_quantities=None):
193 203
         """
194
-        Consume the stock allocations for the passed lines
204
+        Consume the stock allocations for the passed lines.
205
+
206
+        If no lines/quantities are passed, do it for all lines.
195 207
         """
208
+        if not lines:
209
+            lines = order.lines.all()
210
+        if not line_quantities:
211
+            line_quantities = [line.quantity for line in lines]
196 212
         for line, qty in zip(lines, line_quantities):
197 213
             if line.stockrecord:
198 214
                 line.stockrecord.consume_allocation(qty)
199 215
 
200
-    def cancel_stock_allocations(self, order, lines, line_quantities):
216
+    def cancel_stock_allocations(self, order, lines=None, line_quantities=None):
201 217
         """
202
-        Cancel the stock allocations for the passed lines
218
+        Cancel the stock allocations for the passed lines.
219
+
220
+        If no lines/quantities are passed, do it for all lines.
203 221
         """
222
+        if not lines:
223
+            lines = order.lines.all()
224
+        if not line_quantities:
225
+            line_quantities = [line.quantity for line in lines]
204 226
         for line, qty in zip(lines, line_quantities):
205 227
             if line.stockrecord:
206 228
                 line.stockrecord.cancel_allocation(qty)

+ 13
- 0
src/oscar/apps/partner/abstract_models.py Zobrazit soubor

@@ -2,6 +2,7 @@ from django.db import models, router
2 2
 from django.db.models import F, Value, signals
3 3
 from django.db.models.functions import Coalesce
4 4
 from django.utils.encoding import python_2_unicode_compatible
5
+from django.utils.functional import cached_property
5 6
 from django.utils.timezone import now
6 7
 from django.utils.translation import ugettext_lazy as _
7 8
 from django.utils.translation import pgettext_lazy
@@ -180,6 +181,11 @@ class AbstractStockRecord(models.Model):
180 181
             return self.num_in_stock
181 182
         return self.num_in_stock - self.num_allocated
182 183
 
184
+    @cached_property
185
+    def can_track_allocations(self):
186
+        """Return True if the Product is set for stock tracking."""
187
+        return self.product.product_class.track_stock
188
+
183 189
     # 2-stage stock management model
184 190
 
185 191
     def allocate(self, quantity):
@@ -190,6 +196,9 @@ class AbstractStockRecord(models.Model):
190 196
         product is actually shipped, then we 'consume' the allocation.
191 197
 
192 198
         """
199
+        # Doesn't make sense to allocate if stock tracking is off.
200
+        if not self.can_track_allocations:
201
+            return
193 202
         # Send the pre-save signal
194 203
         signals.pre_save.send(
195 204
             sender=self.__class__,
@@ -232,6 +241,8 @@ class AbstractStockRecord(models.Model):
232 241
         This is used when an item is shipped.  We remove the original
233 242
         allocation and adjust the number in stock accordingly
234 243
         """
244
+        if not self.can_track_allocations:
245
+            return
235 246
         if not self.is_allocation_consumption_possible(quantity):
236 247
             raise InvalidStockAdjustment(
237 248
                 _('Invalid stock consumption request'))
@@ -241,6 +252,8 @@ class AbstractStockRecord(models.Model):
241 252
     consume_allocation.alters_data = True
242 253
 
243 254
     def cancel_allocation(self, quantity):
255
+        if not self.can_track_allocations:
256
+            return
244 257
         # We ignore requests that request a cancellation of more than the
245 258
         # amount already allocated.
246 259
         self.num_allocated -= min(self.num_allocated, quantity)

+ 183
- 0
tests/integration/order/test_event_handler.py Zobrazit soubor

@@ -57,6 +57,189 @@ class TestEventHandler(TestCase):
57 57
             self.handler.handle_shipping_event(
58 58
                 order, self.shipped, lines, [4])
59 59
 
60
+    def test_are_stock_allocations_available(self):
61
+        product_class = factories.ProductClassFactory(
62
+            requires_shipping=False, track_stock=True)
63
+        product = factories.ProductFactory(product_class=product_class)
64
+
65
+        basket = factories.create_basket(empty=True)
66
+        add_product(basket, D('10.00'), 5, product=product)
67
+        order = factories.create_order(basket=basket)
68
+
69
+        line = order.lines.get()
70
+        self.assertEqual(
71
+            self.handler.are_stock_allocations_available(
72
+                [line], [line.quantity]),
73
+            True,
74
+        )
75
+
76
+        self.assertEqual(
77
+            self.handler.are_stock_allocations_available(
78
+                [line], [105]),
79
+            False,
80
+        )
81
+
82
+    def test_are_stock_allocations_available_track_stock_off(self):
83
+        product_class = factories.ProductClassFactory(
84
+            requires_shipping=False, track_stock=False)
85
+        product = factories.ProductFactory(product_class=product_class)
86
+        basket = factories.create_basket(empty=True)
87
+        add_product(basket, D('10.00'), 5, product=product)
88
+        order = factories.create_order(basket=basket)
89
+
90
+        line = order.lines.get()
91
+        self.assertEqual(
92
+            self.handler.are_stock_allocations_available(
93
+                [line], [105]),
94
+            True,
95
+        )
96
+
97
+    def test_consume_stock_allocations_track_stock_on(self):
98
+        product_class = factories.ProductClassFactory(
99
+            requires_shipping=False, track_stock=True)
100
+        product = factories.ProductFactory(product_class=product_class)
101
+        basket = factories.create_basket(empty=True)
102
+        add_product(basket, D('10.00'), 5, product=product)
103
+        order = factories.create_order(basket=basket)
104
+
105
+        stockrecord = product.stockrecords.get()
106
+        num_in_stock = stockrecord.num_in_stock
107
+        num_allocated = stockrecord.num_allocated
108
+
109
+        lines = order.lines.all()
110
+        self.handler.consume_stock_allocations(
111
+            order, lines, [line.quantity for line in lines])
112
+
113
+        stockrecord.refresh_from_db()
114
+        self.assertEqual(
115
+            stockrecord.num_allocated,
116
+            num_allocated - 5,
117
+            "Allocated stock should have decreased, but didn't."
118
+        )
119
+        self.assertEqual(
120
+            stockrecord.num_in_stock,
121
+            num_in_stock - 5,
122
+            "Stock should have decreased, but didn't."
123
+        )
124
+
125
+    def test_consume_stock_allocations_track_stock_off(self):
126
+        product_class = factories.ProductClassFactory(
127
+            requires_shipping=False, track_stock=False)
128
+        product = factories.ProductFactory(product_class=product_class)
129
+        basket = factories.create_basket(empty=True)
130
+        add_product(basket, D('10.00'), 5, product=product)
131
+        order = factories.create_order(basket=basket)
132
+
133
+        stockrecord = product.stockrecords.get()
134
+        num_in_stock = stockrecord.num_in_stock
135
+        num_allocated = stockrecord.num_allocated
136
+
137
+        lines = order.lines.all()
138
+        self.handler.consume_stock_allocations(
139
+            order, lines, [line.quantity for line in lines])
140
+
141
+        stockrecord.refresh_from_db()
142
+        self.assertEqual(
143
+            stockrecord.num_allocated,
144
+            num_allocated,
145
+            "Allocated stock shouldn't have changed, but it did."
146
+        )
147
+        self.assertEqual(
148
+            stockrecord.num_in_stock,
149
+            num_in_stock,
150
+            "Stock shouldn't have changed, but it did."
151
+        )
152
+
153
+    def test_consume_stock_allocations_without_line_arguments(self):
154
+        product_class = factories.ProductClassFactory(
155
+            requires_shipping=False, track_stock=True)
156
+        product = factories.ProductFactory(product_class=product_class)
157
+        basket = factories.create_basket(empty=True)
158
+        add_product(basket, D('10.00'), 5, product=product)
159
+        order = factories.create_order(basket=basket)
160
+
161
+        stockrecord = product.stockrecords.get()
162
+        num_in_stock = stockrecord.num_in_stock
163
+        num_allocated = stockrecord.num_allocated
164
+
165
+        self.handler.consume_stock_allocations(order)
166
+
167
+        stockrecord.refresh_from_db()
168
+        self.assertEqual(
169
+            stockrecord.num_allocated,
170
+            num_allocated - 5,
171
+            "Allocated stock should have decreased, but didn't."
172
+        )
173
+        self.assertEqual(
174
+            stockrecord.num_in_stock,
175
+            num_in_stock - 5,
176
+            "Stock should have decreased, but didn't."
177
+        )
178
+
179
+    def test_cancel_stock_allocations_track_stock_on(self):
180
+        product_class = factories.ProductClassFactory(
181
+            requires_shipping=False, track_stock=True)
182
+        product = factories.ProductFactory(product_class=product_class)
183
+        basket = factories.create_basket(empty=True)
184
+        add_product(basket, D('10.00'), 5, product=product)
185
+        order = factories.create_order(basket=basket)
186
+
187
+        stockrecord = product.stockrecords.get()
188
+        num_allocated = stockrecord.num_allocated
189
+
190
+        lines = order.lines.all()
191
+        self.handler.cancel_stock_allocations(
192
+            order, lines, [line.quantity for line in lines])
193
+
194
+        stockrecord.refresh_from_db()
195
+        self.assertEqual(
196
+            stockrecord.num_allocated,
197
+            num_allocated - 5,
198
+            "Allocated stock should have decreased, but didn't."
199
+        )
200
+
201
+    def test_cancel_stock_allocations_track_stock_off(self):
202
+        product_class = factories.ProductClassFactory(
203
+            requires_shipping=False, track_stock=False)
204
+        product = factories.ProductFactory(product_class=product_class)
205
+        basket = factories.create_basket(empty=True)
206
+        add_product(basket, D('10.00'), 5, product=product)
207
+        order = factories.create_order(basket=basket)
208
+
209
+        stockrecord = product.stockrecords.get()
210
+        num_allocated = stockrecord.num_allocated
211
+
212
+        lines = order.lines.all()
213
+        self.handler.cancel_stock_allocations(
214
+            order, lines, [line.quantity for line in lines])
215
+
216
+        stockrecord.refresh_from_db()
217
+        self.assertEqual(
218
+            stockrecord.num_allocated,
219
+            num_allocated,
220
+            "Allocated stock shouldn't have changed, but it did."
221
+        )
222
+
223
+    def test_cancel_stock_allocations_without_line_arguments(self):
224
+        product_class = factories.ProductClassFactory(
225
+            requires_shipping=False, track_stock=True)
226
+        product = factories.ProductFactory(product_class=product_class)
227
+        basket = factories.create_basket(empty=True)
228
+        add_product(basket, D('10.00'), 5, product=product)
229
+        order = factories.create_order(basket=basket)
230
+
231
+        stockrecord = product.stockrecords.get()
232
+        num_allocated = stockrecord.num_allocated
233
+
234
+        self.handler.cancel_stock_allocations(order)
235
+
236
+        stockrecord.refresh_from_db()
237
+        self.assertEqual(
238
+            stockrecord.num_allocated,
239
+            num_allocated - 5,
240
+            "Allocated stock should have decreased, but didn't."
241
+        )
242
+
60 243
 
61 244
 class TestTotalCalculation(TestCase):
62 245
 

+ 14
- 0
tests/integration/partner/test_models.py Zobrazit soubor

@@ -55,6 +55,20 @@ class TestStockRecord(TestCase):
55 55
         self.assertEqual(10, self.stockrecord.num_in_stock)
56 56
 
57 57
 
58
+class TestStockRecordNoStockTrack(TestCase):
59
+
60
+    def setUp(self):
61
+        product_class = factories.ProductClassFactory(
62
+            requires_shipping=False, track_stock=False)
63
+        self.product = factories.ProductFactory(product_class=product_class)
64
+        self.stockrecord = factories.create_stockrecord(
65
+            self.product, price_excl_tax=D('10.00'), num_in_stock=10)
66
+
67
+    def test_allocate_does_nothing(self):
68
+        self.stockrecord.allocate(5)
69
+        self.assertEqual(self.stockrecord.num_allocated, None)
70
+
71
+
58 72
 class TestPartnerAddress(TestCase):
59 73
 
60 74
     def setUp(self):

Načítá se…
Zrušit
Uložit