1
0
forked from erp-dev/erp

feat: added filter param for stock record list api. Support purchase order bind stock change record that is already exists

This commit is contained in:
2026-02-03 18:48:44 +08:00
parent b2d252875e
commit f2243a02f4
9 changed files with 379 additions and 2 deletions

View File

@@ -28,6 +28,9 @@ class ListStockChangesView(StockChangeViewMixin, views.APIView):
OpenApiParameter(name='start', type=OpenApiTypes.DATE, description='开始日期 (YYYY-MM-DD),默认为昨天'),
OpenApiParameter(name='end', type=OpenApiTypes.DATE, description='结束日期 (YYYY-MM-DD),默认为昨天'),
OpenApiParameter(name='type', type=OpenApiTypes.INT, description='变动类型 (1=入库, 2=出库)'),
OpenApiParameter(name='source_type', type=OpenApiTypes.INT, description='来源类型 (如采购=1)'),
OpenApiParameter(name='source_id', type=OpenApiTypes.INT, description='来源业务单据ID'),
OpenApiParameter(name='source_id_isnull', type=OpenApiTypes.BOOL, description='来源业务单据ID是否为空'),
OpenApiParameter(name='warehouse', type=OpenApiTypes.INT, description='仓库ID'),
OpenApiParameter(name='product', type=OpenApiTypes.INT, description='产品ID'),
OpenApiParameter(name='is_finished', type=OpenApiTypes.BOOL, description='是否已完成'),
@@ -45,6 +48,9 @@ class ListStockChangesView(StockChangeViewMixin, views.APIView):
- start: 开始日期 (YYYY-MM-DD),可选,默认为昨天
- end: 结束日期 (YYYY-MM-DD),可选,默认为昨天
- type: 变动类型 (1=入库, 2=出库),可选
- source_type: 来源类型(可选)
- source_id: 来源业务单据ID可选
- source_id_isnull: 来源业务单据ID是否为空可选true/false
- warehouse: 仓库ID可选
- product: 产品ID可选
- is_finished: 是否已完成 (true/false),可选
@@ -106,6 +112,31 @@ class ListStockChangesView(StockChangeViewMixin, views.APIView):
return Response({
'error': 'type 参数必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
source_type = request.GET.get('source_type')
if source_type:
try:
queryset = queryset.filter(source_type=int(source_type))
except ValueError:
return Response({
'error': 'source_type 参数必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
source_id = request.GET.get('source_id')
if source_id:
try:
queryset = queryset.filter(source_id=int(source_id))
except ValueError:
return Response({
'error': 'source_id 参数必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
else:
source_id_isnull = request.GET.get('source_id_isnull')
if source_id_isnull is not None:
if source_id_isnull.lower() == 'true':
queryset = queryset.filter(source_id__isnull=True)
elif source_id_isnull.lower() == 'false':
queryset = queryset.filter(source_id__isnull=False)
warehouse_id = request.GET.get('warehouse')
if warehouse_id:

View File

@@ -1,4 +1,5 @@
from decimal import Decimal
import datetime
import logging
import unittest
@@ -541,7 +542,7 @@ class ListStockChangeDetailsAPITestCase(TestCase):
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('缺少必要参数', response.data['error'])
self.assertIn('warehouse_id', response.data['message'])
def test_list_stock_change_details_missing_product_id(self):
"""测试缺少product_id参数"""
@@ -665,6 +666,90 @@ class ListStockChangeDetailsAPITestCase(TestCase):
self.assertEqual(result['record_type'], stock_models.StockChangeTypeEnum.REMOVE)
self.assertEqual(result['record_type_display'], '出库')
class ListStockChangeRecordsAPITestCase(TestCase):
"""测试库存变动记录查询 API 的新增筛选参数"""
def setUp(self):
self.client = APIClient()
self.merchant = basic_models.Merchant.objects.create(
name='记录商户',
type=basic_models.MerchantTypeEnum.FACTORY,
)
self.category = basic_models.ProductCategory.objects.create(
merchant=self.merchant,
name='布料',
product_prefix='FAB',
)
self.product = basic_models.Product.objects.create(
merchant=self.merchant,
category=self.category,
name='测试布料',
human_id='FAB-100',
unit=basic_models.ProductUnitEnum.METER,
)
self.warehouse = basic_models.WareHouse.objects.create(
merchant=self.merchant,
name='记录仓库',
mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
)
self.user = User.objects.create_user(username='record_user', password='pass123')
self.employee = basic_models.Employee.objects.create(
merchant=self.merchant,
sys_user=self.user,
name='仓管员',
mobile='13800138009',
status=basic_models.EmployeeStatusEnum.ACTIVE,
)
self.client.force_authenticate(user=self.user)
self.record_unbound = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
source_id=None,
created_by=self.user,
)
stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
stock_change_record=self.record_unbound,
product=self.product,
quantity='10',
unit=self.product.unit,
)
self.record_bound = stock_models.StockChangeRecord.objects.create(
merchant=self.merchant,
type=stock_models.StockChangeTypeEnum.ADD,
warehouse=self.warehouse,
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
source_id=99,
created_by=self.user,
)
stock_models.StockChangeDetail.objects.create(
merchant=self.merchant,
stock_change_record=self.record_bound,
product=self.product,
quantity='5',
unit=self.product.unit,
)
def test_list_stock_change_records_filter_source_type_and_source_id_isnull(self):
today = datetime.date.today().strftime('%Y-%m-%d')
response = self.client.get(
f'/api/v1/stock-changes/?start={today}&end={today}'
f'&type={stock_models.StockChangeTypeEnum.ADD}'
f'&source_type={stock_models.StockChangeSourceEnum.PURCHASE}'
f'&source_id_isnull=true'
f'&warehouse={self.warehouse.id}'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
result_ids = [item['id'] for item in response.data['results']]
self.assertIn(self.record_unbound.id, result_ids)
self.assertNotIn(self.record_bound.id, result_ids)
def test_list_stock_change_details_with_invalid_direction(self):
"""测试无效的direction参数"""
response = self.client.get(