1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/stock_change_views/list.py

222 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
库存变动记录列表查询视图
"""
from rest_framework import status, views
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from datetime import datetime, timedelta
import logging
from stock import models as stock_models
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
from .mixins import StockChangeViewMixin
logger = logging.getLogger(__name__)
class ListStockChangesView(StockChangeViewMixin, views.APIView):
"""获取库存变动记录列表"""
permission_classes = [IsAuthenticated]
@extend_schema(
tags=['读取出入库列表'],
parameters=[
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='warehouse', type=OpenApiTypes.INT, description='仓库ID'),
OpenApiParameter(name='product', type=OpenApiTypes.INT, description='产品ID'),
OpenApiParameter(name='is_finished', type=OpenApiTypes.BOOL, description='是否已完成'),
OpenApiParameter(name='include_details', type=OpenApiTypes.BOOL, description='是否包含明细默认true'),
],
responses={200: dict},
summary="获取库存变动记录列表",
description="按时间范围查询库存变动记录,支持多种过滤条件"
)
def get(self, request):
"""
获取库存变动记录列表(按时间范围查询)
查询参数:
- start: 开始日期 (YYYY-MM-DD),可选,默认为昨天
- end: 结束日期 (YYYY-MM-DD),可选,默认为昨天
- type: 变动类型 (1=入库, 2=出库),可选
- warehouse: 仓库ID可选
- product: 产品ID可选
- is_finished: 是否已完成 (true/false),可选
- include_details: 是否包含明细记录 (true/false),可选,默认为 true
"""
# 检查员工权限
if not self.check_employee_permission(request):
return self.permission_error_response('无权限访问')
merchant_id = self.get_merchant_id(request)
try:
# 获取查询参数
start_date_str = request.GET.get('start')
end_date_str = request.GET.get('end')
# 默认查询昨天的数据
if not start_date_str and not end_date_str:
yesterday = datetime.now().date() - timedelta(days=1)
start_date = yesterday
end_date = yesterday
else:
# 解析日期参数
try:
if start_date_str:
start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date()
else:
start_date = datetime.now().date() - timedelta(days=1)
if end_date_str:
end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date()
else:
end_date = start_date
except ValueError:
return Response({
'error': '日期格式错误,请使用 YYYY-MM-DD 格式'
}, status=status.HTTP_400_BAD_REQUEST)
# 验证日期范围
if start_date > end_date:
return Response({
'error': '开始日期不能大于结束日期'
}, status=status.HTTP_400_BAD_REQUEST)
# 构建查询
queryset = stock_models.StockChangeRecord.objects.filter(
merchant_id=merchant_id,
created_at__date__gte=start_date,
created_at__date__lte=end_date
).select_related('warehouse', 'created_by')
# 可选过滤条件
change_type = request.GET.get('type')
if change_type:
try:
queryset = queryset.filter(type=int(change_type))
except ValueError:
return Response({
'error': 'type 参数必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
warehouse_id = request.GET.get('warehouse')
if warehouse_id:
try:
warehouse_id = int(warehouse_id)
# 验证仓库是否对用户可见
if not self.validate_warehouse_visibility(warehouse_id, request):
return Response({
'error': f'仓库ID {warehouse_id} 对当前用户不可见'
}, status=status.HTTP_403_FORBIDDEN)
queryset = queryset.filter(warehouse_id=warehouse_id)
except ValueError:
return Response({
'error': 'warehouse 参数必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
# 产品过滤参数
product_id = request.GET.get('product')
if product_id:
try:
product_id = int(product_id)
# 验证产品是否对用户可见
if not self.validate_product_visibility(product_id, request):
return Response({
'error': f'产品ID {product_id} 对当前用户不可见'
}, status=status.HTTP_403_FORBIDDEN)
# 只返回包含该产品的库存变动记录
queryset = queryset.filter(details__product_id=product_id).distinct()
except ValueError:
return Response({
'error': 'product 参数必须为整数'
}, status=status.HTTP_400_BAD_REQUEST)
is_finished = request.GET.get('is_finished')
if is_finished is not None:
if is_finished.lower() == 'true':
queryset = queryset.filter(is_finished=True)
elif is_finished.lower() == 'false':
queryset = queryset.filter(is_finished=False)
# 是否包含明细记录,默认为 true
include_details_str = request.GET.get('include_details', 'true')
include_details = include_details_str.lower() != 'false'
# 过滤掉用户不可见的仓库和产品的记录
visible_records = []
for record in queryset:
# 检查仓库可见性
if not self.validate_warehouse_visibility(record.warehouse_id, request):
continue
# 检查该记录是否包含至少一个用户可见的产品
has_visible_product = False
record_details = stock_models.StockChangeDetail.objects.filter(
stock_change_record=record
).select_related('product')
for detail in record_details:
if self.validate_product_visibility(detail.product_id, request):
has_visible_product = True
break
# 只有当记录包含至少一个可见产品时才添加
if has_visible_product:
visible_records.append(record)
# 构建响应数据
results = []
for record in visible_records:
# 获取该记录的所有明细
all_details = stock_models.StockChangeDetail.objects.filter(
stock_change_record=record
).select_related('product')
# 只统计和返回用户可见的产品明细
visible_details = self.filter_visible_details(all_details, request)
total_quantity = sum(float(detail.quantity) for detail in visible_details)
record_data = self.build_record_data(record)
record_data['details_count'] = len(visible_details)
record_data['total_quantity'] = total_quantity
# 如果需要包含明细,则添加明细数据
if include_details:
record_data['details'] = [self.build_detail_data(detail) for detail in visible_details]
results.append(record_data)
response_data = {
'count': len(results),
'results': results,
'date_range': {
'start': start_date.strftime('%Y-%m-%d'),
'end': end_date.strftime('%Y-%m-%d')
}
}
logger.info(
f"用户 {request.user.username} 查询库存变动记录列表,"
f"时间范围: {start_date}{end_date},共 {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)