150 lines
6.0 KiB
Python
150 lines
6.0 KiB
Python
"""开放平台接口实现:
|
|
1. APIKeyViewSet: 面向内部管理员的密钥管理(生成、吊销、列表)
|
|
2. 开放业务端点(OpenProductView, OpenStockView, OpenOrderView):
|
|
面向第三方 ERP / 下游分销 / 电商对接系统,使用 APIKeyAuthentication 认证。
|
|
"""
|
|
|
|
from decimal import Decimal
|
|
from datetime import date
|
|
from rest_framework import status, views, permissions
|
|
from rest_framework.response import Response
|
|
|
|
from apps.core import audit as audit_log
|
|
from rest_framework.exceptions import ValidationError
|
|
from asgiref.sync import sync_to_async
|
|
from adrf.viewsets import ViewSet
|
|
|
|
from apps.core.viewset import BaseTenantViewSet
|
|
from apps.catalog.models import Product
|
|
from apps.inventory.models import Stock, Warehouse
|
|
from apps.partner.models import Customer
|
|
from apps.sales.models import SalesBill, SalesBillLine
|
|
from apps.sales import services as sales_services
|
|
|
|
from .models import APIKey
|
|
from .serializers import (
|
|
APIKeySerializer, APIKeyCreateInputSerializer,
|
|
ExternalOrderCreateSerializer,
|
|
)
|
|
from .auth import APIKeyAuthentication, require_scope
|
|
|
|
|
|
class APIKeyViewSet(BaseTenantViewSet):
|
|
"""内部管理:API Key 维护。"""
|
|
model = APIKey
|
|
serializer_class = APIKeySerializer
|
|
search_fields = ["name", "prefix"]
|
|
|
|
async def acreate(self, request, *args, **kwargs):
|
|
tenant = await self.get_tenant()
|
|
if not tenant:
|
|
raise ValidationError({"tenant": "无法识别租户"})
|
|
|
|
serializer = APIKeyCreateInputSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
data = serializer.validated_data
|
|
|
|
user = request.user if request.user.is_authenticated else None
|
|
key_obj, raw_key = await sync_to_async(APIKey.generate)(
|
|
tenant=tenant,
|
|
name=data["name"],
|
|
scopes=data.get("scopes"),
|
|
expires_at=data.get("expires_at"),
|
|
created_by=user,
|
|
)
|
|
# P0-5:允许创建时指定 rate_limit(下界 1 由 serializer 保证)。
|
|
if data.get("rate_limit") is not None:
|
|
key_obj.rate_limit = data["rate_limit"]
|
|
await sync_to_async(key_obj.save)(update_fields=["rate_limit"])
|
|
|
|
res_data = APIKeySerializer(key_obj).data
|
|
res_data["raw_key"] = raw_key # 仅在创建时返回一次明文密钥!
|
|
return Response(res_data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
class OpenProductListView(views.APIView):
|
|
"""第三方开放接口:获取商品列表及定价。"""
|
|
authentication_classes = [APIKeyAuthentication]
|
|
permission_classes = [require_scope("products:read")]
|
|
|
|
def get(self, request):
|
|
tenant = getattr(request, "tenant_obj", None) or getattr(getattr(request, "_request", None), "tenant_obj", None)
|
|
products = Product.objects.filter(tenant=tenant, status="active").values(
|
|
"code", "name", "spec", "barcode", "sale_price"
|
|
)
|
|
return Response({"count": len(products), "results": list(products)})
|
|
|
|
|
|
class OpenStockListView(views.APIView):
|
|
"""第三方开放接口:实时查询商品可用库存。"""
|
|
authentication_classes = [APIKeyAuthentication]
|
|
permission_classes = [require_scope("stocks:read")]
|
|
|
|
def get(self, request):
|
|
tenant = getattr(request, "tenant_obj", None) or getattr(getattr(request, "_request", None), "tenant_obj", None)
|
|
stocks = Stock.objects.filter(tenant=tenant, on_hand__gt=0).select_related("product", "warehouse")
|
|
results = []
|
|
for s in stocks:
|
|
results.append({
|
|
"product_code": s.product.code,
|
|
"product_name": s.product.name,
|
|
"warehouse_code": s.warehouse.code,
|
|
"warehouse_name": s.warehouse.name,
|
|
"on_hand": s.on_hand,
|
|
"available": s.on_hand - s.locked,
|
|
})
|
|
return Response({"count": len(results), "results": results})
|
|
|
|
|
|
class OpenOrderCreateView(views.APIView):
|
|
"""第三方开放接口:外部系统推送销售开单。"""
|
|
authentication_classes = [APIKeyAuthentication]
|
|
permission_classes = [require_scope("orders:write")]
|
|
|
|
def post(self, request):
|
|
tenant = getattr(request, "tenant_obj", None) or getattr(getattr(request, "_request", None), "tenant_obj", None)
|
|
serializer = ExternalOrderCreateSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
data = serializer.validated_data
|
|
|
|
customer = Customer.objects.filter(tenant=tenant, code=data["customer_code"], is_active=True).first()
|
|
if not customer:
|
|
return Response({"detail": f"客户编码 {data['customer_code']} 不存在"}, status=400)
|
|
|
|
wh_code = data.get("warehouse_code")
|
|
if wh_code:
|
|
warehouse = Warehouse.objects.filter(tenant=tenant, code=wh_code, is_active=True).first()
|
|
else:
|
|
warehouse = Warehouse.objects.filter(tenant=tenant, is_active=True).first()
|
|
|
|
if not warehouse:
|
|
return Response({"detail": "未指定可用仓库"}, status=400)
|
|
|
|
parsed_lines = []
|
|
for item in data["lines"]:
|
|
p = Product.objects.filter(tenant=tenant, code=item["product_code"], status="active").first()
|
|
if not p:
|
|
p = Product.objects.filter(tenant=tenant, code=item["product_code"]).first()
|
|
if not p:
|
|
return Response({"detail": f"商品编码 {item['product_code']} 不存在"}, status=400)
|
|
parsed_lines.append({
|
|
"product": p,
|
|
"quantity": item["quantity"],
|
|
"unit_price": item.get("unit_price", p.sale_price),
|
|
})
|
|
|
|
bill = sales_services.create_sales_bill(
|
|
tenant=tenant,
|
|
customer=customer,
|
|
warehouse=warehouse,
|
|
lines=parsed_lines,
|
|
remark=f"[API接入] {data.get('remark', '')}",
|
|
)
|
|
|
|
return Response({
|
|
"ok": True,
|
|
"bill_no": bill.bill_no,
|
|
"total_amount": bill.total_amount,
|
|
"status": bill.state,
|
|
}, status=status.HTTP_201_CREATED)
|