forked from erp-dev/erp
178 lines
6.3 KiB
Python
178 lines
6.3 KiB
Python
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
|
||
import logging
|
||
|
||
from flower.viewsets import LimitedLimitOffsetPagination
|
||
from stock import models as stock_models
|
||
from basic_info import models as basic_models
|
||
from api_man.serializers import ProductSerializer
|
||
|
||
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 = LimitedLimitOffsetPagination
|
||
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)
|