1
0
forked from erp-dev/erp

feat: sse && multi_merchant completed

This commit is contained in:
2025-11-11 10:56:20 +08:00
parent b2078dfa46
commit 2aafb93aad
43 changed files with 2445 additions and 244 deletions

View File

@@ -0,0 +1,221 @@
"""
库存变动记录列表查询视图
"""
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)