"""inventory async ViewSets。 库存账面与流水是只读对外接口(写入走 services); 仓库可增删改。 """ from adrf.viewsets import ModelViewSet, ReadOnlyModelViewSet from datetime import timedelta from django.utils import timezone from apps.core.viewset import BaseTenantViewSet from apps.core.viewset import StandardAsyncPagination from .models import Warehouse, Stock, StockBatch, StockMovement from .serializers import WarehouseSerializer, StockSerializer, StockMovementSerializer, StockBatchSerializer class WarehouseViewSet(BaseTenantViewSet): model = Warehouse serializer_class = WarehouseSerializer search_fields = ["code", "name"] class StockViewSet(BaseTenantViewSet): model = Stock serializer_class = StockSerializer search_fields = ["product__code", "product__name", "warehouse__code"] http_method_names = ["get", "head", "options"] # 只读 select_related_fields = ("product", "warehouse", "product__base_unit") class StockMovementViewSet(BaseTenantViewSet): model = StockMovement serializer_class = StockMovementSerializer search_fields = ["product__code", "product__name", "source_ref"] http_method_names = ["get", "head", "options"] # 只读 select_related_fields = ("product", "warehouse") class StockBatchViewSet(BaseTenantViewSet): """批次库存(只读;写入走 inbound/outbound 服务)。""" model = StockBatch serializer_class = StockBatchSerializer search_fields = ["batch_no", "product__code", "product__name", "warehouse__code"] http_method_names = ["get", "head", "options"] # 只读 select_related_fields = ("product", "warehouse", "product__base_unit") async def get_queryset(self): qs = await super().get_queryset() # 支持按 product / warehouse 过滤,且默认只看有量批次 product_id = self.request.query_params.get("product") warehouse_id = self.request.query_params.get("warehouse") in_stock = self.request.query_params.get("in_stock", "") near_expiry = self.request.query_params.get("near_expiry", "") expiry_days = self.request.query_params.get("expiry_days", "30") if product_id: qs = qs.filter(product_id=product_id) if warehouse_id: qs = qs.filter(warehouse_id=warehouse_id) if in_stock in ("1", "true", "True"): qs = qs.filter(on_hand__gt=0) if near_expiry in ("1", "true", "True"): try: days = max(0, min(int(expiry_days), 3650)) except (TypeError, ValueError): days = 30 cutoff = timezone.localdate() + timedelta(days=days) qs = qs.filter(expiry_date__isnull=False, expiry_date__lte=cutoff) return qs