import datetime import logging from fastapi import APIRouter, Depends, Query from typing import Any, Union, Optional from pydantic import ValidationError from alien_store.api.deps import get_contract_service, get_contract_center_service from alien_contract.schemas.request.contract import BundleCreateRequest from alien_contract.services.contract_server import ContractCenterService from alien_store.schemas.request.contract_store import TemplatesCreate from alien_store.schemas.response.contract_store import ( ModuleStatusResponse, TemplatesCreateResponse, ErrorResponse, ContractStoreResponse, PaginatedResponse, SuccessResponse ) from alien_store.services.contract_server import ContractServer router = APIRouter() logger = logging.getLogger("alien_store") def _format_validation_errors(exc: ValidationError) -> list[dict[str, str]]: errors = [] for err in exc.errors(): loc = err.get("loc", ()) field = ".".join(str(item) for item in loc if item != "body") errors.append( { "field": field or "body", "type": err.get("type", "validation_error"), "message": err.get("msg", "参数校验失败"), } ) return errors @router.get("/", response_model=ModuleStatusResponse) async def index() -> ModuleStatusResponse: return ModuleStatusResponse(module="Contract", status="Ok") @router.post("/get_esign_templates", response_model=Union[TemplatesCreateResponse, ErrorResponse]) async def create_esign_templates( templates_data_raw: dict[str, Any], templates_server: ContractCenterService = Depends(get_contract_center_service) ) -> Union[TemplatesCreateResponse, ErrorResponse]: """AI审核完调用 e签宝生成文件""" try: templates_data = TemplatesCreate.model_validate(templates_data_raw) except ValidationError as e: detail = _format_validation_errors(e) logger.error("get_esign_templates validation failed: %s", detail) return ErrorResponse( success=False, message="请求参数校验失败", raw={"errors": detail}, ) request = BundleCreateRequest( subject_type="store", subject_id=templates_data.store_id, subject_name=templates_data.store_name, business_segment=templates_data.business_segment, contact_name=templates_data.merchant_name, contact_phone=templates_data.contact_phone, ord_id=templates_data.ord_id, bundle_type="STORE_STANDARD", ) result = await templates_server.create_bundle(request) if not result.get("success"): return ErrorResponse(**result) return TemplatesCreateResponse( success=True, message=result["message"], sign_flow_id=result.get("primary_sign_flow_id"), file_id=result.get("created_contracts", [{}])[0].get("file_id") if result.get("created_contracts") else None, contract_url=result.get("created_contracts", [{}])[0].get("contract_url") if result.get("created_contracts") else None, created_contracts=result.get("created_contracts"), ) @router.get("/contracts/{store_id}", response_model=Union[dict, Any]) async def list_contracts( store_id: int, status: Optional[int] = Query(None, description="筛选合同状态:0 未签署,1 已签署"), page: int = Query(1, ge=1, description="页码,从1开始"), page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"), templates_server: ContractServer = Depends(get_contract_service) ) -> Any: """根据 store_id 查询所有合同,支持根据 status 筛选和分页""" return await templates_server.list_contracts(store_id, status, page, page_size) @router.get("/contracts/detail/{sign_flow_id}", response_model=Union[dict, ErrorResponse]) async def get_contract_detail( sign_flow_id: str, templates_server: ContractServer = Depends(get_contract_service) ) -> Union[dict, ErrorResponse]: """ 根据 sign_flow_id 获取合同详情 - status=0: 返回合同PDF链接(contract_url)和签署链接(sign_url) - status=1: 拉取最新下载链接并更新数据库,返回 contract_download_url """ result = await templates_server.get_contract_detail(sign_flow_id) if not result.get("success", True): # get_contract_detail 返回的成功结果里没有 success 键,只有 status return ErrorResponse(**result) return result @router.get("/get_all_templates", response_model=PaginatedResponse) async def get_all_templates( page: int = Query(1, ge=1, description="页码,从1开始"), page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"), store_name: Optional[str] = Query(None, description="店铺名称(模糊查询)"), merchant_name: Optional[str] = Query(None, description="商家姓名(模糊查询)"), signing_status: Optional[str] = Query(None, description="签署状态"), business_segment: Optional[str] = Query(None, description="经营板块"), store_status: Optional[str] = Query(None, description="店铺状态:正常/禁用"), expiry_start: Optional[datetime.datetime] = Query(None, description="到期时间起"), expiry_end: Optional[datetime.datetime] = Query(None, description="到期时间止"), templates_server: ContractServer = Depends(get_contract_service) ) -> PaginatedResponse: """分页查询所有合同,支持筛选""" rows, total = await templates_server.list_all_paged( page, page_size, store_name=store_name, merchant_name=merchant_name, signing_status=signing_status, business_segment=business_segment, store_status=store_status, expiry_start=expiry_start, expiry_end=expiry_end, ) total_pages = (total + page_size - 1) // page_size if total > 0 else 0 items = [ContractStoreResponse(**row) for row in rows] return PaginatedResponse( items=items, total=total, page=page, page_size=page_size, total_pages=total_pages ) @router.post("/esign/callback", response_model=Union[SuccessResponse, ErrorResponse]) async def esign_callback( payload: dict, templates_server: ContractServer = Depends(get_contract_service) ) -> Union[SuccessResponse, ErrorResponse]: """ e签宝签署结果回调 需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1 """ result = await templates_server.process_esign_callback(payload) if not result.get("success"): return ErrorResponse(**result) return SuccessResponse(code=result["code"], msg=result["msg"]) # @router.post("/esign/callback_auth", response_model=SuccessResponse) # async def esign_callback_auth( # payload: dict, # templates_server: ContractServer = Depends(get_contract_service) # ) -> SuccessResponse: # logger.info(f"esign_callback_auth payload: {payload}") # return SuccessResponse(code="200", msg="success")