baseline: 批次A-D 成果 + membership 半成品(测试红)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
"""商品中心 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/<id>/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)
|
||||
Reference in New Issue
Block a user