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

176
api_v1/views/inventory.py Normal file
View File

@@ -0,0 +1,176 @@
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import serializers, status
from rest_framework.pagination import LimitOffsetPagination
from stock import models as stock_models
from basic_info import models as basic_models
from api_man.serializers import ProductSerializer
import logging
logger = logging.getLogger(__name__)
class WarehouseSimpleSerializer(serializers.ModelSerializer):
"""仓库简单序列化器"""
class Meta:
model = basic_models.WareHouse
fields = ['id', 'name', 'location', 'area']
class InventorySerializer(serializers.ModelSerializer):
"""库存项序列化器"""
product = ProductSerializer(read_only=True)
warehouse = WarehouseSimpleSerializer(read_only=True)
class Meta:
model = stock_models.Inventory
fields = [
'id',
'product',
'warehouse',
'quantity',
'num_of_rolls',
'spec',
]
def __init__(self, *args, **kwargs):
"""重写初始化方法,确保 context 传递给嵌套序列化器"""
super().__init__(*args, **kwargs)
# 如果有 context将其传递给嵌套的序列化器字段
if hasattr(self, 'context'):
for field_name, field in self.fields.items():
if isinstance(field, serializers.ModelSerializer):
field.context.update(self.context)
class InventoryAPIView(ListModelMixin, GenericAPIView):
"""
库存查询接口
只返回当前用户所属商户的库存信息
查询参数:
- limit: 返回的记录数量默认20最大100
- offset: 跳过的记录数量默认0
- warehouse: 仓库ID可选
- product: 产品ID可选
示例:
- /api/v1/inventory/ - 获取前20条
- /api/v1/inventory/?limit=50&offset=0 - 获取前50条
- /api/v1/inventory/?limit=20&offset=40 - 跳过前40条获取接下来的20条
"""
serializer_class = InventorySerializer
pagination_class = LimitOffsetPagination
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""
获取查询集,自动过滤当前用户所属商户的库存
"""
# 检查用户是否有员工身份
if not hasattr(self.request.user, 'employee'):
logger.warning(f"用户 {self.request.user.username} 无员工信息,拒绝访问库存")
return stock_models.Inventory.objects.none()
employee = self.request.user.employee
merchant_id = employee.merchant_id
# 使用 select_related 优化查询,只查询当前商户的库存
queryset = stock_models.Inventory.objects.filter(
merchant_id=merchant_id
).select_related(
'product',
'product__category',
'warehouse'
).order_by('-quantity', 'product__name')
# 可选过滤:按仓库
warehouse_id = self.request.query_params.get('warehouse')
if warehouse_id:
try:
warehouse_id = int(warehouse_id)
# 验证仓库属于当前商户
if basic_models.WareHouse.objects.filter(
id=warehouse_id,
merchant_id=merchant_id
).exists():
queryset = queryset.filter(warehouse_id=warehouse_id)
else:
logger.warning(f"仓库ID {warehouse_id} 不属于商户 {merchant_id}")
return queryset.none()
except ValueError:
logger.warning(f"warehouse 参数格式错误: {warehouse_id}")
return queryset.none()
# 可选过滤:按产品
product_id = self.request.query_params.get('product')
if product_id:
try:
product_id = int(product_id)
# 验证产品属于当前商户
if basic_models.Product.objects.filter(
id=product_id,
merchant_id=merchant_id
).exists():
queryset = queryset.filter(product_id=product_id)
else:
logger.warning(f"产品ID {product_id} 不属于商户 {merchant_id}")
return queryset.none()
except ValueError:
logger.warning(f"product 参数格式错误: {product_id}")
return queryset.none()
return queryset
def get_serializer_context(self):
"""
确保序列化器获得 request context
"""
context = super().get_serializer_context()
context['request'] = self.request
return context
def list(self, request, *args, **kwargs):
"""
重写 list 方法,添加统计信息
"""
queryset = self.filter_queryset(self.get_queryset())
# 统计信息(在分页前)
total_items = queryset.count()
total_quantity = sum(item.quantity for item in queryset)
total_rolls = sum(item.num_of_rolls for item in queryset)
# 使用 ListModelMixin 的分页和序列化逻辑
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
response = self.get_paginated_response(serializer.data)
# 添加统计信息
response.data['statistics'] = {
'total_items': total_items,
'total_quantity': float(total_quantity),
'total_rolls': total_rolls,
}
logger.info(
f"用户 {request.user.username} 查询库存列表,"
f"商户ID: {request.user.employee.merchant_id},共 {total_items} 条记录"
)
return response
# 如果没有分页,返回全部数据
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def get(self, request, *args, **kwargs):
"""
GET 请求处理
"""
return self.list(request, *args, **kwargs)