111 lines
3.9 KiB
Python
111 lines
3.9 KiB
Python
"""商品中心 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)
|