"""开放平台接口实现: 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)