router.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import datetime
  2. import logging
  3. from fastapi import APIRouter, Depends, Query
  4. from typing import Any, Union, Optional
  5. from pydantic import ValidationError
  6. from alien_store.api.deps import get_contract_service, get_contract_center_service
  7. from alien_contract.schemas.request.contract import BundleCreateRequest
  8. from alien_contract.services.contract_server import ContractCenterService
  9. from alien_store.schemas.request.contract_store import TemplatesCreate
  10. from alien_store.schemas.response.contract_store import (
  11. ModuleStatusResponse,
  12. TemplatesCreateResponse,
  13. ErrorResponse,
  14. ContractStoreResponse,
  15. PaginatedResponse,
  16. SuccessResponse
  17. )
  18. from alien_store.services.contract_server import ContractServer
  19. router = APIRouter()
  20. logger = logging.getLogger("alien_store")
  21. def _format_validation_errors(exc: ValidationError) -> list[dict[str, str]]:
  22. errors = []
  23. for err in exc.errors():
  24. loc = err.get("loc", ())
  25. field = ".".join(str(item) for item in loc if item != "body")
  26. errors.append(
  27. {
  28. "field": field or "body",
  29. "type": err.get("type", "validation_error"),
  30. "message": err.get("msg", "参数校验失败"),
  31. }
  32. )
  33. return errors
  34. @router.get("/", response_model=ModuleStatusResponse)
  35. async def index() -> ModuleStatusResponse:
  36. return ModuleStatusResponse(module="Contract", status="Ok")
  37. @router.post("/get_esign_templates", response_model=Union[TemplatesCreateResponse, ErrorResponse])
  38. async def create_esign_templates(
  39. templates_data_raw: dict[str, Any],
  40. templates_server: ContractCenterService = Depends(get_contract_center_service)
  41. ) -> Union[TemplatesCreateResponse, ErrorResponse]:
  42. """AI审核完调用 e签宝生成文件"""
  43. try:
  44. templates_data = TemplatesCreate.model_validate(templates_data_raw)
  45. except ValidationError as e:
  46. detail = _format_validation_errors(e)
  47. logger.error("get_esign_templates validation failed: %s", detail)
  48. return ErrorResponse(
  49. success=False,
  50. message="请求参数校验失败",
  51. raw={"errors": detail},
  52. )
  53. request = BundleCreateRequest(
  54. subject_type="store",
  55. subject_id=templates_data.store_id,
  56. subject_name=templates_data.store_name,
  57. business_segment=templates_data.business_segment,
  58. contact_name=templates_data.merchant_name,
  59. contact_phone=templates_data.contact_phone,
  60. ord_id=templates_data.ord_id,
  61. bundle_type="STORE_STANDARD",
  62. )
  63. result = await templates_server.create_bundle(request)
  64. if not result.get("success"):
  65. return ErrorResponse(**result)
  66. return TemplatesCreateResponse(
  67. success=True,
  68. message=result["message"],
  69. sign_flow_id=result.get("primary_sign_flow_id"),
  70. file_id=result.get("created_contracts", [{}])[0].get("file_id") if result.get("created_contracts") else None,
  71. contract_url=result.get("created_contracts", [{}])[0].get("contract_url") if result.get("created_contracts") else None,
  72. created_contracts=result.get("created_contracts"),
  73. )
  74. @router.get("/contracts/{store_id}", response_model=Union[dict, Any])
  75. async def list_contracts(
  76. store_id: int,
  77. status: Optional[int] = Query(None, description="筛选合同状态:0 未签署,1 已签署"),
  78. page: int = Query(1, ge=1, description="页码,从1开始"),
  79. page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
  80. templates_server: ContractServer = Depends(get_contract_service)
  81. ) -> Any:
  82. """根据 store_id 查询所有合同,支持根据 status 筛选和分页"""
  83. return await templates_server.list_contracts(store_id, status, page, page_size)
  84. @router.get("/contracts/detail/{sign_flow_id}", response_model=Union[dict, ErrorResponse])
  85. async def get_contract_detail(
  86. sign_flow_id: str,
  87. templates_server: ContractServer = Depends(get_contract_service)
  88. ) -> Union[dict, ErrorResponse]:
  89. """
  90. 根据 sign_flow_id 获取合同详情
  91. - status=0: 返回合同PDF链接(contract_url)和签署链接(sign_url)
  92. - status=1: 拉取最新下载链接并更新数据库,返回 contract_download_url
  93. """
  94. result = await templates_server.get_contract_detail(sign_flow_id)
  95. if not result.get("success", True): # get_contract_detail 返回的成功结果里没有 success 键,只有 status
  96. return ErrorResponse(**result)
  97. return result
  98. @router.get("/get_all_templates", response_model=PaginatedResponse)
  99. async def get_all_templates(
  100. page: int = Query(1, ge=1, description="页码,从1开始"),
  101. page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
  102. store_name: Optional[str] = Query(None, description="店铺名称(模糊查询)"),
  103. merchant_name: Optional[str] = Query(None, description="商家姓名(模糊查询)"),
  104. signing_status: Optional[str] = Query(None, description="签署状态"),
  105. business_segment: Optional[str] = Query(None, description="经营板块"),
  106. store_status: Optional[str] = Query(None, description="店铺状态:正常/禁用"),
  107. expiry_start: Optional[datetime.datetime] = Query(None, description="到期时间起"),
  108. expiry_end: Optional[datetime.datetime] = Query(None, description="到期时间止"),
  109. templates_server: ContractServer = Depends(get_contract_service)
  110. ) -> PaginatedResponse:
  111. """分页查询所有合同,支持筛选"""
  112. rows, total = await templates_server.list_all_paged(
  113. page,
  114. page_size,
  115. store_name=store_name,
  116. merchant_name=merchant_name,
  117. signing_status=signing_status,
  118. business_segment=business_segment,
  119. store_status=store_status,
  120. expiry_start=expiry_start,
  121. expiry_end=expiry_end,
  122. )
  123. total_pages = (total + page_size - 1) // page_size if total > 0 else 0
  124. items = [ContractStoreResponse(**row) for row in rows]
  125. return PaginatedResponse(
  126. items=items,
  127. total=total,
  128. page=page,
  129. page_size=page_size,
  130. total_pages=total_pages
  131. )
  132. @router.post("/esign/callback", response_model=Union[SuccessResponse, ErrorResponse])
  133. async def esign_callback(
  134. payload: dict,
  135. templates_server: ContractServer = Depends(get_contract_service)
  136. ) -> Union[SuccessResponse, ErrorResponse]:
  137. """
  138. e签宝签署结果回调
  139. 需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1
  140. """
  141. result = await templates_server.process_esign_callback(payload)
  142. if not result.get("success"):
  143. return ErrorResponse(**result)
  144. return SuccessResponse(code=result["code"], msg=result["msg"])
  145. # @router.post("/esign/callback_auth", response_model=SuccessResponse)
  146. # async def esign_callback_auth(
  147. # payload: dict,
  148. # templates_server: ContractServer = Depends(get_contract_service)
  149. # ) -> SuccessResponse:
  150. # logger.info(f"esign_callback_auth payload: {payload}")
  151. # return SuccessResponse(code="200", msg="success")