Files

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)