baseline: 批次A-D 成果 + membership 半成品(测试红)

This commit is contained in:
agent
2026-09-11 23:11:35 +08:00
commit b3f3095d53
311 changed files with 40540 additions and 0 deletions
+110
View File
@@ -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)