1
0
forked from erp-dev/erp

feat: query api for stock_change_detail

This commit is contained in:
2025-11-25 18:08:42 +08:00
parent 9b8f6dd57c
commit 6bf0465d05
5 changed files with 434 additions and 2 deletions

View File

@@ -219,3 +219,142 @@ class ListStockChangesView(StockChangeViewMixin, views.APIView):
'error': '查询库存变动记录列表失败',
'message': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ListStockChangeDetailsView(StockChangeViewMixin, views.APIView):
"""获取库存变动明细列表"""
permission_classes = [IsAuthenticated]
@extend_schema(
tags=['读取库存明细'],
parameters=[
OpenApiParameter(name='warehouse_id', type=OpenApiTypes.INT, required=True, description='仓库ID'),
OpenApiParameter(name='product_id', type=OpenApiTypes.INT, required=True, description='产品ID'),
OpenApiParameter(name='direction', type=OpenApiTypes.STR, required=False, description='变动方向 (inbound=入库, outbound=出库)默认为inbound'),
],
responses={200: dict},
summary="获取库存变动明细列表",
description="根据仓库和产品查询库存变动明细不查询StockChangeRecord"
)
def get(self, request):
"""
获取库存变动明细列表(根据仓库和产品查询)
查询参数:
- warehouse_id: 仓库ID必填
- product_id: 产品ID必填
- direction: 变动方向 (inbound=入库, outbound=出库)可选默认为inbound
"""
# 检查员工权限
if not self.check_employee_permission(request):
return self.permission_error_response('无权限访问')
merchant_id = self.get_merchant_id(request)
try:
# 获取查询参数
warehouse_id_str = request.GET.get('warehouse_id')
product_id_str = request.GET.get('product_id')
direction = request.GET.get('direction', 'inbound') # 默认为入库
# 验证必填参数
if not warehouse_id_str:
return Response({
'error': '缺少必要参数',
'message': 'warehouse_id 参数为必填'
}, status=status.HTTP_400_BAD_REQUEST)
if not product_id_str:
return Response({
'error': '缺少必要参数',
'message': 'product_id 参数为必填'
}, status=status.HTTP_400_BAD_REQUEST)
# 验证direction参数
if direction not in ['inbound', 'outbound']:
return Response({
'error': '参数格式错误',
'message': 'direction 参数必须为 inbound 或 outbound'
}, status=status.HTTP_400_BAD_REQUEST)
# 解析参数
try:
warehouse_id = int(warehouse_id_str)
product_id = int(product_id_str)
except ValueError:
return Response({
'error': '参数格式错误',
'message': 'warehouse_id 和 product_id 必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
# 验证仓库是否对用户可见
if not self.validate_warehouse_visibility(warehouse_id, request):
return Response({
'error': f'仓库ID {warehouse_id} 对当前用户不可见'
}, status=status.HTTP_403_FORBIDDEN)
# 验证产品是否对用户可见
if not self.validate_product_visibility(product_id, request):
return Response({
'error': f'产品ID {product_id} 对当前用户不可见'
}, status=status.HTTP_403_FORBIDDEN)
# 根据direction参数确定变动类型
if direction == 'inbound':
change_type = stock_models.StockChangeTypeEnum.ADD # 入库
else: # outbound
change_type = stock_models.StockChangeTypeEnum.REMOVE # 出库
# 查询库存变动明细仅查询未被消耗的明细consumed_by_detail为空
details = stock_models.StockChangeDetail.objects.filter(
stock_change_record__merchant_id=merchant_id,
stock_change_record__warehouse_id=warehouse_id,
stock_change_record__type=change_type,
product_id=product_id,
consumed_by_detail__isnull=True # 仅查询未被消耗的明细
).select_related(
'stock_change_record',
'product'
).order_by('-stock_change_record__created_at')
# 构建响应数据
results = []
for detail in details:
detail_data = self.build_detail_data(detail)
# 添加记录的额外信息
detail_data['record_type'] = detail.stock_change_record.type
detail_data['record_type_display'] = detail.stock_change_record.get_type_display()
detail_data['source_type'] = detail.stock_change_record.source_type
detail_data['source_type_display'] = detail.stock_change_record.get_source_type_display()
detail_data['record_created_at'] = detail.stock_change_record.created_at
detail_data['record_created_by'] = detail.stock_change_record.created_by.username
results.append(detail_data)
response_data = {
'count': len(results),
'results': results,
'filters': {
'warehouse_id': warehouse_id,
'product_id': product_id,
'direction': direction
}
}
logger.info(
f"用户 {request.user.username} 查询库存变动明细,"
f"仓库ID: {warehouse_id}产品ID: {product_id},共 {len(results)}"
)
return Response(response_data, status=status.HTTP_200_OK)
except AttributeError as e:
logger.error(f"用户 {request.user.username} 无员工信息: {str(e)}", exc_info=True)
return self.permission_error_response('用户无员工信息,无权访问')
except Exception as e:
logger.error(f"查询库存变动明细失败: {str(e)}", exc_info=True)
return Response({
'error': '查询库存变动明细失败',
'message': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)