"""客户申报投顾方案 + 投顾受理审批 的 HTTP 入口(`docs/05` 未登记的 AD 段新端点)。 | 段 | 端点 | 权限码 | 摘要 | |---|---|---|---| | 客户 | `POST /api/v1/users/me/advisor-requests` | `advisor-request:write:self` | 提交申报 | | 客户 | `GET /api/v1/users/me/advisor-requests` | `advisor-request:read:self` | 我的申报(含状态) | | 投顾 | `GET /api/v1/advisor/service-requests` | `advisor-request:read` | 名下客户的申报队列 | | 投顾 | `POST /api/v1/advisor/service-requests/{request_no}/reviews` | `advisor-request:review` | 受理 / 驳回 | 客户侧刻意**不挂投顾灰度闸门**(`enforce_advisor_rollout`)—— 那是投顾业务的灰度, 客户提交自己的服务申请不该被它拦下;投顾侧照旧挂上。 """ from __future__ import annotations from fastapi import APIRouter, Depends, Header, Path from app.api.dependencies.auth import build_request_context from app.api.dependencies.rate_limit import enforce_rate_limit from app.core.contracts import RequestContext from app.core.errors import ValidationAgentError from app.service.advisor_rollout_service import enforce_advisor_rollout from app.service.advisor_service_request_service import ( AdvisorServiceRequestCreate, AdvisorServiceRequestService, ) client_router = APIRouter( prefix="/api/v1/users/me", tags=["advisor-service-requests"], dependencies=[Depends(enforce_rate_limit)], ) advisor_router = APIRouter( prefix="/api/v1/advisor", tags=["advisor-service-requests"], dependencies=[Depends(enforce_rate_limit), Depends(enforce_advisor_rollout)], ) @client_router.post("/advisor-requests") async def create_advisor_request( payload: AdvisorServiceRequestCreate, context: RequestContext = Depends(build_request_context), # noqa: B008 key: str | None = Header(default=None, alias="Idempotency-Key"), ) -> dict[str, object]: """客户主动申报投顾方案(需已完成且未过期的风险测评)。""" return await AdvisorServiceRequestService().create(payload, context, key) @client_router.get("/advisor-requests") async def my_advisor_requests( context: RequestContext = Depends(build_request_context), # noqa: B008 ) -> dict[str, object]: """我的申报单(待受理 / 已受理 / 已发送 / 已驳回)。""" return await AdvisorServiceRequestService().list_mine(context) @advisor_router.get("/service-requests") async def advisor_service_request_queue( context: RequestContext = Depends(build_request_context), # noqa: B008 ) -> dict[str, object]: """名下客户提交的申报队列(待受理排前面)。""" return await AdvisorServiceRequestService().queue(context) @advisor_router.post("/service-requests/{request_no}/reviews") async def review_advisor_service_request( payload: dict[str, object], request_no: str = Path(min_length=8, max_length=32), context: RequestContext = Depends(build_request_context), # noqa: B008 key: str | None = Header(default=None, alias="Idempotency-Key"), ) -> dict[str, object]: """受理(**自动生成方案草稿**)或驳回客户的申报。 受理后投顾仍需走既有的「审核通过 → 发送给客户」,方案才会到客户手里。 """ decision = payload.get("decision") if decision not in {"accepted", "rejected"}: raise ValidationAgentError("decision 必须为 accepted 或 rejected") comment = payload.get("comment", "") if not isinstance(comment, str): raise ValidationAgentError("comment 必须是字符串") return await AdvisorServiceRequestService().review( request_no, str(decision), comment, context, key )