Merge pull request 'Restock product inventory when an order is cancelled' (#9) from fix/restock-on-cancel into master

Reviewed-on: #9
This commit is contained in:
Ghasemi 2026-09-05 08:16:24 -04:00
commit aff0e77339
2 changed files with 38 additions and 0 deletions

View file

@ -1,11 +1,14 @@
from itertools import groupby
from django.db import transaction
from django.db.models import F
from django.utils import timezone
from rest_framework.exceptions import ValidationError
from utils.exceptions import Conflict
from apps.catalog.models import Product
from .models import Cart, Notification, Order, OrderGroup, OrderItem, OrderStatusLog
@ -136,6 +139,16 @@ def checkout(user, address, delivery_type, scheduled_at, payment_method, notes):
return order_group
def _restock_order_items(order):
"""Reverses the stock_quantity/sold_count decrement made at checkout (see checkout())."""
for item in order.items.select_related('product').filter(product__isnull=False):
Product.objects.filter(pk=item.product_id).update(
stock_quantity=F('stock_quantity') + item.quantity,
sold_count=F('sold_count') - item.quantity,
)
@transaction.atomic
def _transition(order, to_status, *, changed_by=None, note='', **extra_fields):
from_status = order.status
order.status = to_status
@ -147,6 +160,7 @@ def _transition(order, to_status, *, changed_by=None, note='', **extra_fields):
OrderStatusLog.objects.create(order=order, from_status=from_status, to_status=to_status, changed_by=changed_by, note=note)
if to_status == Order.Status.CANCELLED:
_restock_order_items(order)
notify(
recipient=order.store.owner,
type=Notification.Type.ORDER_CANCELLED,

View file

@ -50,6 +50,30 @@ class StatusTransitionTests(OrdersTestCase):
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'):