"""商品中心 async ViewSets(多租户基类来自 apps.core.viewset)。""" from decimal import Decimal from asgiref.sync import sync_to_async from adrf.viewsets import ModelViewSet from rest_framework.decorators import action from rest_framework.response import Response from apps.core.viewset import BaseTenantViewSet, StandardAsyncPagination from .models import Category, Brand, Unit, Product, UnitConversion from .serializers import ( CategorySerializer, BrandSerializer, UnitSerializer, ProductSerializer, ) class CategoryViewSet(BaseTenantViewSet): model = Category serializer_class = CategorySerializer search_fields = ["code", "name"] class BrandViewSet(BaseTenantViewSet): model = Brand serializer_class = BrandSerializer search_fields = ["code", "name"] class UnitViewSet(BaseTenantViewSet): model = Unit serializer_class = UnitSerializer search_fields = ["code", "name"] class ProductViewSet(BaseTenantViewSet): model = Product serializer_class = ProductSerializer search_fields = ["code", "name", "barcode", "spec"] quota_kind = "products" # 套餐配额:商品数上限(批次 C1) @action(detail=True, methods=["get"], url_path="units") async def units(self, request, pk=None): """商品的可用录入单位与换算价:GET /api/v1/catalog/products//units/ 返回基本单位 + 全部 UnitConversion(rate 与按 rate 折算的销售价), 供开单页"选择录入单位"使用。 """ tenant = await self.get_tenant() if tenant is None: from rest_framework.exceptions import ValidationError raise ValidationError({"tenant": "无法识别租户"}) def _load(): product = ( Product.objects.filter(tenant=tenant, pk=pk) .select_related("base_unit") .first() ) if product is None: return None convs = list( UnitConversion.objects.filter(product=product) .select_related("unit") .order_by("unit__code") ) base_unit = product.base_unit rows = [] if base_unit is not None: rows.append({ "unit_id": base_unit.id, "unit_code": base_unit.code, "unit_name": base_unit.name, "rate": "1", "is_base": True, "price": str(product.sale_price), "cost_price": str(product.cost_price), }) for c in convs: rows.append({ "unit_id": c.unit_id, "unit_code": c.unit.code, "unit_name": c.unit.name, "rate": str(c.rate), "is_base": False, "price": str((product.sale_price * c.rate).quantize(Decimal("0.0001"))), "cost_price": str((product.cost_price * c.rate).quantize(Decimal("0.0001"))), }) return { "product_id": product.id, "product_code": product.code, "product_name": product.name, "base_unit_id": base_unit.id if base_unit else None, "base_unit_name": base_unit.name if base_unit else "", "sale_price": str(product.sale_price), "cost_price": str(product.cost_price), "min_sale_price": str(product.min_sale_price or 0), "tax_rate": str(product.tax_rate or 0), "is_batch_managed": product.is_batch_managed, "shelf_life_days": product.shelf_life_days, "units": rows, } data = await sync_to_async(_load)() if data is None: from rest_framework.exceptions import NotFound raise NotFound("product not found") return Response(data)