router.py 6.0 KB

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