From dee1c814b9ca5af86af2139678330e522d1783b5 Mon Sep 17 00:00:00 2001 From: colaftc Date: Mon, 2 Feb 2026 19:09:06 +0800 Subject: [PATCH] fix: frozen excluded from stock api --- api_v1/views/stock_change_views/list.py | 2 + .../test_stock_change_api.py | 23 ++++++ business/services.py | 23 ++++++ business/tests/test_sales_order.py | 82 +++++++++++++++++++ 4 files changed, 130 insertions(+) diff --git a/api_v1/views/stock_change_views/list.py b/api_v1/views/stock_change_views/list.py index 3dd9a2a..1b328ed 100644 --- a/api_v1/views/stock_change_views/list.py +++ b/api_v1/views/stock_change_views/list.py @@ -314,6 +314,8 @@ class ListStockChangeDetailsView(StockChangeViewMixin, views.APIView): stock_change_record__type=change_type, product_id=product_id, consumed_by_detail__isnull=True # 仅查询未被消耗的明细 + ).exclude( + stock_freezes__status=stock_models.StockFreezeStatusEnum.FROZEN ).select_related( 'stock_change_record', 'product' diff --git a/api_v1/views/stock_change_views/test_stock_change_api.py b/api_v1/views/stock_change_views/test_stock_change_api.py index 4e56d43..3b84016 100644 --- a/api_v1/views/stock_change_views/test_stock_change_api.py +++ b/api_v1/views/stock_change_views/test_stock_change_api.py @@ -511,6 +511,29 @@ class ListStockChangeDetailsAPITestCase(TestCase): dates = [result['record_created_at'] for result in response.data['results']] self.assertEqual(dates, sorted(dates, reverse=True)) + def test_list_stock_change_details_excludes_frozen(self): + """库存明细列表应过滤已冻结明细""" + frozen_detail = self.details1[0] + stock_models.StockFreeze.objects.create( + merchant=self.merchant, + product=frozen_detail.product, + warehouse=self.warehouse, + stock_detail=frozen_detail, + quantity=frozen_detail.quantity, + unit=frozen_detail.unit, + status=stock_models.StockFreezeStatusEnum.FROZEN, + frozen_by=self.user, + frozen_with=1, + ) + + response = self.client.get( + f'/api/v1/stock-change-details/?warehouse_id={self.warehouse.id}&product_id={self.product.id}' + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + returned_ids = [item['id'] for item in response.data['results']] + self.assertNotIn(frozen_detail.id, returned_ids) + self.assertEqual(response.data['count'], 1) + def test_list_stock_change_details_missing_warehouse_id(self): """测试缺少warehouse_id参数""" response = self.client.get( diff --git a/business/services.py b/business/services.py index 50f561d..0f8be72 100644 --- a/business/services.py +++ b/business/services.py @@ -8,6 +8,7 @@ from typing import Any, Dict, Iterable, List, Tuple, Optional from django.contrib.auth import get_user_model from django.db import transaction +from django.utils import timezone from basic_info import models as basic_info_models from stock import models as stock_models @@ -1273,6 +1274,28 @@ def _approve_sales_order( source_id=locked_order.id, ) + consume_detail_ids: List[int] = [] + for item in locked_order.items.all(): + raw_ids = (item.consume_detail_ids or '').strip() + if not raw_ids: + continue + consume_detail_ids.extend( + int(value) + for value in raw_ids.split(',') + if value.strip() + ) + if consume_detail_ids: + stock_models.StockFreeze.objects.filter( + merchant=locked_order.merchant, + stock_detail_id__in=consume_detail_ids, + status=stock_models.StockFreezeStatusEnum.FROZEN, + ).update( + status=stock_models.StockFreezeStatusEnum.COMPLETED, + completed_by=reviewed_by, + completed_at=timezone.now(), + completed_with=locked_order.id, + ) + created_by_id = getattr(reviewed_by, 'id', None) if _auto_stock_task_enabled(locked_order.merchant): logger.info('审批通过销售单 %s,触发出库任务', locked_order.id) diff --git a/business/tests/test_sales_order.py b/business/tests/test_sales_order.py index 007d2b6..47a9d0d 100644 --- a/business/tests/test_sales_order.py +++ b/business/tests/test_sales_order.py @@ -194,6 +194,88 @@ class SalesOrderServiceTestCase(TestCase): self.assertEqual(item.consume_detail_ids, ','.join(consume_ids)) self.assertEqual(item.quantity_of_rolls, '6,4') + def test_review_sales_order_unfreezes_consumed_stock(self): + stock_record = stock_models.StockChangeRecord.objects.create( + merchant=self.merchant, + type=stock_models.StockChangeTypeEnum.ADD, + warehouse=self.warehouse_strict_out, + source_type=stock_models.StockChangeSourceEnum.PURCHASE, + created_by=self.user, + ) + detail_1 = stock_models.StockChangeDetail.objects.create( + merchant=self.merchant, + product=self.product, + unit=self.product.unit, + stock_change_record=stock_record, + quantity='6', + ) + detail_2 = stock_models.StockChangeDetail.objects.create( + merchant=self.merchant, + product=self.product, + unit=self.product.unit, + stock_change_record=stock_record, + quantity='4', + ) + stock_models.StockFreeze.objects.create( + merchant=self.merchant, + product=self.product, + warehouse=self.warehouse_strict_out, + stock_detail=detail_1, + quantity=detail_1.quantity, + unit=detail_1.unit, + status=stock_models.StockFreezeStatusEnum.FROZEN, + frozen_by=self.user, + frozen_with=1, + ) + stock_models.StockFreeze.objects.create( + merchant=self.merchant, + product=self.product, + warehouse=self.warehouse_strict_out, + stock_detail=detail_2, + quantity=detail_2.quantity, + unit=detail_2.unit, + status=stock_models.StockFreezeStatusEnum.FROZEN, + frozen_by=self.user, + frozen_with=2, + ) + + sales_order = services.create_sales_order( + merchant=self.merchant, + customer=self.customer, + order_date=timezone.now().date(), + warehouse=self.warehouse_strict_out, + operator=self.operator, + items=[ + { + 'product_id': self.product.id, + 'consume_detail_ids': [detail_1.id, detail_2.id], + 'quantity': 10, + 'price': '32.0', + 'unit': '米', + } + ], + created_by=self.user, + ) + + with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay: + reviewed = services.review_sales_order( + sales_order=sales_order, + target_status=business_models.SalesOrderStatusEnum.APPROVED, + reviewed_by=self.user, + ) + self.assertEqual(reviewed.status, business_models.SalesOrderStatusEnum.APPROVED) + mock_delay.assert_called_once() + + statuses = list( + stock_models.StockFreeze.objects.filter( + stock_detail_id__in=[detail_1.id, detail_2.id] + ).values_list('status', flat=True) + ) + self.assertEqual( + set(statuses), + {stock_models.StockFreezeStatusEnum.COMPLETED}, + ) + def test_sales_order_outgoing_missing_consume_ids_raises(self): payload = [ {