checkout() decrements stock_quantity/increments sold_count per order item, but cancellation never reversed it, so a cancelled order's stock stayed permanently reduced. Add _restock_order_items(), called from _transition() on the CANCELLED transition, using F() expressions to reverse both counters atomically; wrap _transition() itself in @transaction.atomic so the status change and restock can't partially apply. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
84 lines
3.7 KiB
Python
84 lines
3.7 KiB
Python
from apps.orders.models import Order, OrderGroup
|
|
|
|
from .base import OrdersTestCase
|
|
|
|
|
|
class StatusTransitionTests(OrdersTestCase):
|
|
def setUp(self):
|
|
self.mock_external_clients()
|
|
self.client.force_authenticate(user=self.customer)
|
|
self.add_to_cart(self.product1, quantity=1)
|
|
response = self.client.post('/api/v1/checkout/', {
|
|
'address_uuid': str(self.address.uuid),
|
|
'payment_method': OrderGroup.PaymentMethod.CASH_ON_DELIVERY,
|
|
})
|
|
self.order = Order.objects.get(uuid=response.data['orders'][0]['uuid'])
|
|
|
|
def _as_seller(self):
|
|
self.client.force_authenticate(user=self.seller1)
|
|
|
|
def test_seller_can_walk_the_full_status_stepper(self):
|
|
self._as_seller()
|
|
|
|
for action, expected_status in (
|
|
('confirm', Order.Status.PREPARING),
|
|
('mark-ready', Order.Status.READY_TO_SHIP),
|
|
('mark-shipped', Order.Status.HANDED_TO_COURIER),
|
|
('mark-delivered', Order.Status.DELIVERED),
|
|
):
|
|
response = self.client.post(f'/api/v1/seller/orders/{self.order.uuid}/{action}/')
|
|
self.assertEqual(response.status_code, 200, response.data)
|
|
self.order.refresh_from_db()
|
|
self.assertEqual(self.order.status, expected_status)
|
|
|
|
self.assertEqual(self.order.status_logs.count(), 5) # placed + 4 transitions
|
|
|
|
def test_cannot_skip_a_status(self):
|
|
self._as_seller()
|
|
response = self.client.post(f'/api/v1/seller/orders/{self.order.uuid}/mark-delivered/')
|
|
self.assertEqual(response.status_code, 409)
|
|
|
|
def test_other_seller_cannot_act_on_this_order(self):
|
|
self.client.force_authenticate(user=self.seller2)
|
|
response = self.client.post(f'/api/v1/seller/orders/{self.order.uuid}/confirm/')
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def test_customer_can_cancel_before_preparing(self):
|
|
self.client.force_authenticate(user=self.customer)
|
|
response = self.client.post(f'/api/v1/orders/{self.order.uuid}/cancel/')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.order.refresh_from_db()
|
|
self.assertEqual(self.order.status, Order.Status.CANCELLED)
|
|
|
|
def test_cancelling_an_order_restocks_its_products(self):
|
|
self.product1.refresh_from_db()
|
|
stock_after_checkout = self.product1.stock_quantity
|
|
sold_after_checkout = self.product1.sold_count
|
|
self.assertEqual(stock_after_checkout, 49) # base.py seeds 50, checkout bought 1
|
|
self.assertEqual(sold_after_checkout, 1)
|
|
|
|
self.client.force_authenticate(user=self.customer)
|
|
response = self.client.post(f'/api/v1/orders/{self.order.uuid}/cancel/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
self.product1.refresh_from_db()
|
|
self.assertEqual(self.product1.stock_quantity, stock_after_checkout + 1)
|
|
self.assertEqual(self.product1.sold_count, sold_after_checkout - 1)
|
|
|
|
def test_cancelling_after_confirm_also_restocks(self):
|
|
self._as_seller()
|
|
self.client.post(f'/api/v1/seller/orders/{self.order.uuid}/confirm/')
|
|
self.client.post(f'/api/v1/seller/orders/{self.order.uuid}/cancel/')
|
|
|
|
self.product1.refresh_from_db()
|
|
self.assertEqual(self.product1.stock_quantity, 50)
|
|
self.assertEqual(self.product1.sold_count, 0)
|
|
|
|
def test_cannot_cancel_after_handed_to_courier(self):
|
|
self._as_seller()
|
|
for action in ('confirm', 'mark-ready', 'mark-shipped'):
|
|
self.client.post(f'/api/v1/seller/orders/{self.order.uuid}/{action}/')
|
|
|
|
self.client.force_authenticate(user=self.customer)
|
|
response = self.client.post(f'/api/v1/orders/{self.order.uuid}/cancel/')
|
|
self.assertEqual(response.status_code, 409)
|