|
@@ -1,12 +1,21 @@
|
|
|
import datetime
|
|
import datetime
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from fastapi import APIRouter, Depends, Query
|
|
|
-from typing import Any
|
|
|
|
|
|
|
+from typing import Any, List, Union
|
|
|
import json
|
|
import json
|
|
|
import os
|
|
import os
|
|
|
import logging
|
|
import logging
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
|
from alien_store.api.deps import get_contract_service
|
|
from alien_store.api.deps import get_contract_service
|
|
|
from alien_store.schemas.request.contract_store import TemplatesCreate, SignUrl
|
|
from alien_store.schemas.request.contract_store import TemplatesCreate, SignUrl
|
|
|
|
|
+from alien_store.schemas.response.contract_store import (
|
|
|
|
|
+ ModuleStatusResponse,
|
|
|
|
|
+ TemplatesCreateResponse,
|
|
|
|
|
+ ErrorResponse,
|
|
|
|
|
+ ContractStoreResponse,
|
|
|
|
|
+ PaginatedResponse,
|
|
|
|
|
+ SignUrlResponse,
|
|
|
|
|
+ SuccessResponse
|
|
|
|
|
+)
|
|
|
from alien_store.services.contract_server import ContractServer
|
|
from alien_store.services.contract_server import ContractServer
|
|
|
from common.esigntool.main import *
|
|
from common.esigntool.main import *
|
|
|
import re, urllib.parse
|
|
import re, urllib.parse
|
|
@@ -36,12 +45,15 @@ logger = _init_logger()
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
-@router.get("/")
|
|
|
|
|
-async def index():
|
|
|
|
|
- return {"module": "Contract", "status": "Ok"}
|
|
|
|
|
|
|
+@router.get("/", response_model=ModuleStatusResponse)
|
|
|
|
|
+async def index() -> ModuleStatusResponse:
|
|
|
|
|
+ return ModuleStatusResponse(module="Contract", status="Ok")
|
|
|
|
|
|
|
|
-@router.post("/get_esign_templates")
|
|
|
|
|
-async def create_esign_templates(templates_data: TemplatesCreate, templates_server: ContractServer = Depends(get_contract_service)):
|
|
|
|
|
|
|
+@router.post("/get_esign_templates", response_model=Union[TemplatesCreateResponse, ErrorResponse])
|
|
|
|
|
+async def create_esign_templates(
|
|
|
|
|
+ templates_data: TemplatesCreate,
|
|
|
|
|
+ templates_server: ContractServer = Depends(get_contract_service)
|
|
|
|
|
+) -> Union[TemplatesCreateResponse, ErrorResponse]:
|
|
|
# AI审核完调用 e签宝生成文件
|
|
# AI审核完调用 e签宝生成文件
|
|
|
logger.info(f"get_esign_templates request: {templates_data}")
|
|
logger.info(f"get_esign_templates request: {templates_data}")
|
|
|
res_text = fill_in_template(templates_data.merchant_name)
|
|
res_text = fill_in_template(templates_data.merchant_name)
|
|
@@ -49,7 +61,7 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
|
|
|
res_data = json.loads(res_text)
|
|
res_data = json.loads(res_text)
|
|
|
except json.JSONDecodeError:
|
|
except json.JSONDecodeError:
|
|
|
logger.error(f"fill_in_template non-json resp: {res_text}")
|
|
logger.error(f"fill_in_template non-json resp: {res_text}")
|
|
|
- return {"success": False, "message": "e签宝返回非 JSON", "raw": res_text}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝返回非 JSON", raw=res_text)
|
|
|
# 从返回结构提取下载链接,需与实际返回字段匹配
|
|
# 从返回结构提取下载链接,需与实际返回字段匹配
|
|
|
try:
|
|
try:
|
|
|
contract_url = res_data["data"]["fileDownloadUrl"]
|
|
contract_url = res_data["data"]["fileDownloadUrl"]
|
|
@@ -60,23 +72,23 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
|
|
|
file_name = urllib.parse.unquote(encoded_name)
|
|
file_name = urllib.parse.unquote(encoded_name)
|
|
|
except Exception:
|
|
except Exception:
|
|
|
logger.error(f"fill_in_template missing fileDownloadUrl: {res_data}")
|
|
logger.error(f"fill_in_template missing fileDownloadUrl: {res_data}")
|
|
|
- return {"success": False, "message": "e签宝返回缺少 fileDownloadUrl", "raw": res_data}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝返回缺少 fileDownloadUrl", raw=res_data)
|
|
|
|
|
|
|
|
sign_data = create_by_file(file_id, file_name, templates_data.contact_phone, templates_data.merchant_name)
|
|
sign_data = create_by_file(file_id, file_name, templates_data.contact_phone, templates_data.merchant_name)
|
|
|
try:
|
|
try:
|
|
|
sign_json = json.loads(sign_data)
|
|
sign_json = json.loads(sign_data)
|
|
|
except json.JSONDecodeError:
|
|
except json.JSONDecodeError:
|
|
|
logger.error(f"create_by_file non-json resp: {sign_data}")
|
|
logger.error(f"create_by_file non-json resp: {sign_data}")
|
|
|
- return {"success": False, "message": "e签宝 create_by_file 返回非 JSON", "raw": sign_data}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝 create_by_file 返回非 JSON", raw=sign_data)
|
|
|
|
|
|
|
|
if not sign_json.get("data"):
|
|
if not sign_json.get("data"):
|
|
|
logger.error(f"create_by_file failed or missing data: {sign_json}")
|
|
logger.error(f"create_by_file failed or missing data: {sign_json}")
|
|
|
- return {"success": False, "message": "e签宝创建签署流程失败", "raw": sign_json}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝创建签署流程失败", raw=sign_json)
|
|
|
|
|
|
|
|
sing_id = sign_json["data"].get("signFlowId")
|
|
sing_id = sign_json["data"].get("signFlowId")
|
|
|
if not sing_id:
|
|
if not sing_id:
|
|
|
logger.error(f"create_by_file missing signFlowId: {sign_json}")
|
|
logger.error(f"create_by_file missing signFlowId: {sign_json}")
|
|
|
- return {"success": False, "message": "e签宝返回缺少 signFlowId", "raw": sign_json}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝返回缺少 signFlowId", raw=sign_json)
|
|
|
|
|
|
|
|
result_contract = {
|
|
result_contract = {
|
|
|
"contract_url": contract_url, # 合同模版链接
|
|
"contract_url": contract_url, # 合同模版链接
|
|
@@ -93,25 +105,47 @@ async def create_esign_templates(templates_data: TemplatesCreate, templates_serv
|
|
|
}
|
|
}
|
|
|
updated = await templates_server.append_contract_url(templates_data, result_contract)
|
|
updated = await templates_server.append_contract_url(templates_data, result_contract)
|
|
|
logger.info(f"get_esign_templates success contact_phone={templates_data.contact_phone}, sign_flow_id={sing_id}")
|
|
logger.info(f"get_esign_templates success contact_phone={templates_data.contact_phone}, sign_flow_id={sing_id}")
|
|
|
- return {"success": True, "message": "合同模板已追加/创建", "sign_flow_id": sing_id, "file_id": file_id, "contract_url": contract_url}
|
|
|
|
|
-
|
|
|
|
|
-@router.get("/contracts/{store_id}")
|
|
|
|
|
-async def list_contracts(store_id: int, templates_server: ContractServer = Depends(get_contract_service)) -> Any:
|
|
|
|
|
|
|
+ return TemplatesCreateResponse(
|
|
|
|
|
+ success=True,
|
|
|
|
|
+ message="合同模板已追加/创建",
|
|
|
|
|
+ sign_flow_id=sing_id,
|
|
|
|
|
+ file_id=file_id,
|
|
|
|
|
+ contract_url=contract_url
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+@router.get("/contracts/{store_id}", response_model=List[ContractStoreResponse])
|
|
|
|
|
+async def list_contracts(
|
|
|
|
|
+ store_id: int,
|
|
|
|
|
+ templates_server: ContractServer = Depends(get_contract_service)
|
|
|
|
|
+) -> List[ContractStoreResponse]:
|
|
|
"""根据 store_id 查询所有合同"""
|
|
"""根据 store_id 查询所有合同"""
|
|
|
rows = await templates_server.list_by_store(store_id)
|
|
rows = await templates_server.list_by_store(store_id)
|
|
|
- return rows
|
|
|
|
|
|
|
+ return [ContractStoreResponse(**row) for row in rows]
|
|
|
|
|
|
|
|
-@router.get("/get_all_templates")
|
|
|
|
|
|
|
+@router.get("/get_all_templates", response_model=PaginatedResponse)
|
|
|
async def get_all_templates(
|
|
async def get_all_templates(
|
|
|
page: int = Query(1, ge=1, description="页码,从1开始"),
|
|
page: int = Query(1, ge=1, description="页码,从1开始"),
|
|
|
page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
|
|
page_size: int = Query(10, ge=1, le=100, description="每页条数,默认10"),
|
|
|
templates_server: ContractServer = Depends(get_contract_service)
|
|
templates_server: ContractServer = Depends(get_contract_service)
|
|
|
-) -> Any:
|
|
|
|
|
|
|
+) -> PaginatedResponse:
|
|
|
"""分页查询所有合同"""
|
|
"""分页查询所有合同"""
|
|
|
- return await templates_server.list_all_paged(page, page_size)
|
|
|
|
|
-
|
|
|
|
|
-@router.post("/esign/signurl")
|
|
|
|
|
-async def get_esign_sign_url(body: SignUrl, templates_server: ContractServer = Depends(get_contract_service)):
|
|
|
|
|
|
|
+ rows, total = await templates_server.list_all_paged(page, page_size)
|
|
|
|
|
+ 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/signurl", response_model=Union[SignUrlResponse, ErrorResponse])
|
|
|
|
|
+async def get_esign_sign_url(
|
|
|
|
|
+ body: SignUrl,
|
|
|
|
|
+ templates_server: ContractServer = Depends(get_contract_service)
|
|
|
|
|
+) -> Union[SignUrlResponse, ErrorResponse]:
|
|
|
"""
|
|
"""
|
|
|
当商家点击签署按钮时
|
|
当商家点击签署按钮时
|
|
|
携带合同相关的签署id和联系方式向e签宝发起请求
|
|
携带合同相关的签署id和联系方式向e签宝发起请求
|
|
@@ -127,18 +161,21 @@ async def get_esign_sign_url(body: SignUrl, templates_server: ContractServer = D
|
|
|
result_json = json.loads(result)
|
|
result_json = json.loads(result)
|
|
|
except json.JSONDecodeError:
|
|
except json.JSONDecodeError:
|
|
|
logger.error(f"sign_url non-json resp: {result}")
|
|
logger.error(f"sign_url non-json resp: {result}")
|
|
|
- return {"success": False, "message": "e签宝返回非JSON", "raw": result}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝返回非JSON", raw=result)
|
|
|
data = result_json.get("data") if isinstance(result_json, dict) else None
|
|
data = result_json.get("data") if isinstance(result_json, dict) else None
|
|
|
if not data or not data.get("url"):
|
|
if not data or not data.get("url"):
|
|
|
logger.error(f"sign_url missing url: {result_json}")
|
|
logger.error(f"sign_url missing url: {result_json}")
|
|
|
- return {"success": False, "message": "e签宝返回缺少签署链接", "raw": result_json}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="e签宝返回缺少签署链接", raw=result_json)
|
|
|
result_sign_url = data.get("url")
|
|
result_sign_url = data.get("url")
|
|
|
await templates_server.update_sign_url(contact_phone, sing_flow_id, result_sign_url)
|
|
await templates_server.update_sign_url(contact_phone, sing_flow_id, result_sign_url)
|
|
|
logger.info(f"sign_url success contact_phone={contact_phone}, sign_flow_id={sing_flow_id}")
|
|
logger.info(f"sign_url success contact_phone={contact_phone}, sign_flow_id={sing_flow_id}")
|
|
|
- return {"success": True, "data": {"url": result_sign_url}}
|
|
|
|
|
|
|
+ return SignUrlResponse(success=True, data={"url": result_sign_url})
|
|
|
|
|
|
|
|
-@router.post("/esign/callback")
|
|
|
|
|
-async def esign_callback(payload: dict, templates_server: ContractServer = Depends(get_contract_service)) -> Any:
|
|
|
|
|
|
|
+@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签宝签署结果回调
|
|
e签宝签署结果回调
|
|
|
需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1
|
|
需求:签署完成 -> 更新 signing_status=已签署,contract_url 中 status=1
|
|
@@ -170,12 +207,15 @@ async def esign_callback(payload: dict, templates_server: ContractServer = Depen
|
|
|
|
|
|
|
|
updated = await templates_server.mark_signed_by_phone(contact_phone, sign_flow_id, signing_dt, contract_download_url)
|
|
updated = await templates_server.mark_signed_by_phone(contact_phone, sign_flow_id, signing_dt, contract_download_url)
|
|
|
logger.info(f"esign_callback success phone={contact_phone}, sign_flow_id={sign_flow_id}, updated={updated}")
|
|
logger.info(f"esign_callback success phone={contact_phone}, sign_flow_id={sign_flow_id}, updated={updated}")
|
|
|
- return {"code":"200","msg":"success"}
|
|
|
|
|
|
|
+ return SuccessResponse(code="200", msg="success")
|
|
|
logger.error(f"esign_callback ignored payload: {payload}")
|
|
logger.error(f"esign_callback ignored payload: {payload}")
|
|
|
- return {"success": False, "message": "未处理: signResult!=2 或手机号/签署流程缺失"}
|
|
|
|
|
|
|
+ return ErrorResponse(success=False, message="未处理: signResult!=2 或手机号/签署流程缺失")
|
|
|
|
|
|
|
|
-@router.post("/esign/callback_auth")
|
|
|
|
|
-async def esign_callback_auth(payload: dict, templates_server: ContractServer = Depends(get_contract_service)):
|
|
|
|
|
|
|
+@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}")
|
|
logger.info(f"esign_callback_auth payload: {payload}")
|
|
|
- return {"code":"200","msg":"success"}
|
|
|
|
|
|
|
+ return SuccessResponse(code="200", msg="success")
|
|
|
|
|
|